diff --git a/package.json b/package.json index 15915a3..a105e2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@octomind/octomind", - "version": "4.1.0", + "version": "4.2.0", "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", diff --git a/src/cli.ts b/src/cli.ts index 52c5ccf..eb22c4c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -155,7 +155,7 @@ export const buildCmd = (): CompletableCommand => { .addOption(testTargetIdOption) .option("-e, --environment-name [name]", "Environment name", "default") .option("-d, --description [text]", "Test description") - .option("-g, --tags [tags]", "comma separated list of tags", splitter) + .option("-g, --tags [tags]", "Comma separated list of tags", splitter) .option( "-v, --variables-to-overwrite [variables]", "JSON object of variables to overwrite", @@ -179,20 +179,24 @@ export const buildCmd = (): CompletableCommand => { .completer(optionsCompleter) .description("Execute local YAML test cases") .helpGroup("execute") - .requiredOption("-u, --url ", "url the tests should run against") + .requiredOption("-u, --url ", "Url the tests should run against") + .option( + "-c, --test-case-id [uuid]", + "Id of the test case you want to run, if not provided will run all test cases in the test target", + ) .option( "-e, --environment-id [uuid]", - "id of the environment you want to run against, if not provided will run all test cases against the default environment", + "Id of the environment you want to run against, if not provided will run all test cases against the default environment", ) .option( "-t, --test-target-id [uuid]", - "id of the test target of the test case, if not provided will use the test target id from the config", + "Id of the test target of the test case, if not provided will use the test target id from the config", ) .option( "--headless", - "if we should run headless without the UI of playwright and the browser", + "If we should run headless without the UI of playwright and the browser", ) - .option("--bypass-proxy", "bypass proxy when accessing the test target") + .option("--bypass-proxy", "Bypass proxy when accessing the test target") .option("--browser [CHROMIUM, FIREFOX, SAFARI]", "Browser type", "CHROMIUM") .option("--breakpoint [DESKTOP, MOBILE, TABLET]", "Breakpoint", "DESKTOP") .action(addTestTargetWrapper(executeLocalTestCases)); diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 4de0b5e..0c19c78 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -19,7 +19,9 @@ import { getTestCases, } from "../tools"; import { client, handleError } from "../tools/client"; +import { SyncTestCase } from "../tools/sync/types"; import { readTestCasesFromDir } from "../tools/sync/yaml"; +import { getRelevantTestCases } from "../tools/yamlMutations/getRelevantTestCases"; import { ensureChromiumIsInstalled } from "./installation"; export type DebugtopusOptions = { @@ -177,6 +179,21 @@ const runTests = async ({ } }; +const getFilteredTestCaseWithDependencies = ( + testCases: SyncTestCase[], + filterTestCaseId: string, +): SyncTestCase[] => { + const filteredTestCase = testCases.find( + (testCase) => testCase.id === filterTestCaseId, + ); + if (!filteredTestCase) { + throw new Error(`Could not find test case with id ${filterTestCaseId}`); + } + + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + return getRelevantTestCases(testCasesById, filteredTestCase); +}; + export const runDebugtopus = async (options: DebugtopusOptions) => { const baseApiOptions = { testTargetId: options.testTargetId, @@ -275,12 +292,20 @@ export const executeLocalTestCases = async ( ); } - const testCases = readTestCasesFromDir(octomindRoot); + let testCases = readTestCasesFromDir(octomindRoot); + if (options.testCaseId) { + testCases = getFilteredTestCaseWithDependencies( + testCases, + options.testCaseId, + ); + } + const body = { testCases, testTargetId: options.testTargetId, executionUrl: options.url, environmentId: options.environmentId, + filterTestCaseIds: options.testCaseId ? [options.testCaseId] : undefined, }; const { error, response } = await client.POST( "/apiKey/beta/test-targets/{testTargetId}/code", diff --git a/tests/debugtopus/index.spec.ts b/tests/debugtopus/index.spec.ts index 43c797e..4fba22e 100644 --- a/tests/debugtopus/index.spec.ts +++ b/tests/debugtopus/index.spec.ts @@ -194,11 +194,10 @@ describe("debugtopus", () => { describe("executeLocalTestCases", () => { const OCTOMIND_ROOT = "/project/.octomind"; + const mockZipBuffer = Buffer.from([0x50, 0x4b, 0x03, 0x04]); + const mockConfig = "export default { testDir: './tests' };"; - it("should execute local test cases from zip response body", async () => { - const mockTestCases = [createMockSyncTestCase()]; - const mockZipBuffer = Buffer.from([0x50, 0x4b, 0x03, 0x04]); - const mockConfig = "export default { testDir: './tests' };"; + const setupExecutionMocks = () => { const mockReadableStream = new ReadableStream({ start(controller) { controller.enqueue(mockZipBuffer); @@ -211,33 +210,29 @@ describe("debugtopus", () => { writable: true, configurable: true, }); - const mockDirectory = { extract: vi.fn().mockResolvedValue(undefined) }; vi.mocked(findOctomindFolder).mockResolvedValue(OCTOMIND_ROOT); - mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); mockedClient.POST.mockResolvedValue({ error: undefined, response: mockResponse, data: undefined, }); mockedExistsSync.mockImplementation( - (pathThatShouldExist: Parameters[0]) => { - const pathStr = - typeof pathThatShouldExist === "string" - ? pathThatShouldExist - : pathThatShouldExist.toString(); - return pathStr.includes("node_modules"); - }, + (pathThatShouldExist: Parameters[0]) => + (typeof pathThatShouldExist === "string" + ? pathThatShouldExist + : pathThatShouldExist.toString() + ).includes("node_modules"), ); mockedFs.mkdir.mockResolvedValue(undefined); mockedFs.rm.mockResolvedValue(undefined); mockedCreateWriteStream.mockReturnValue(mockDeep()); mockedPipeline.mockResolvedValue(undefined); mockedFs.readFile.mockResolvedValue(mockZipBuffer); + const mockDirectory = { extract: vi.fn().mockResolvedValue(undefined) }; mockedOpen.buffer = vi.fn().mockResolvedValue(mockDirectory); mockedGetPlaywrightConfig.mockResolvedValue(mockConfig); mockedEnsureChromiumIsInstalled.mockResolvedValue(undefined); - vi.mocked(exec).mockImplementation( ( _command: string, @@ -252,6 +247,12 @@ describe("debugtopus", () => { return mock(); }, ); + }; + + it("should execute local test cases from zip response body", async () => { + const mockTestCases = [createMockSyncTestCase()]; + setupExecutionMocks(); + mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); await executeLocalTestCases({ testTargetId: "test-target-id", @@ -269,6 +270,7 @@ describe("debugtopus", () => { testTargetId: "test-target-id", executionUrl: "https://example.com", environmentId: undefined, + filterTestCaseIds: undefined, }, parseAs: "stream", }), @@ -279,5 +281,45 @@ describe("debugtopus", () => { mockConfig, ); }); + + it("should filter test cases by testCaseId when provided", async () => { + setupExecutionMocks(); + mockedReadTestCasesFromDir.mockReturnValue([ + createMockSyncTestCase({ id: "other-1" }), + createMockSyncTestCase({ id: "target-id" }), + createMockSyncTestCase({ id: "other-2" }), + ]); + + await executeLocalTestCases({ + testTargetId: "test-target-id", + url: "https://example.com", + testCaseId: "target-id", + }); + + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/code", + expect.objectContaining({ + body: expect.objectContaining({ + testCases: [expect.objectContaining({ id: "target-id" })], + filterTestCaseIds: ["target-id"], + }), + }), + ); + }); + + it("should throw an error if testCaseId does not match any test case", async () => { + vi.mocked(findOctomindFolder).mockResolvedValue(OCTOMIND_ROOT); + mockedReadTestCasesFromDir.mockReturnValue([ + createMockSyncTestCase({ id: "test-case-1" }), + ]); + + await expect( + executeLocalTestCases({ + testTargetId: "test-target-id", + url: "https://example.com", + testCaseId: "non-existent-id", + }), + ).rejects.toThrow("Could not find test case with id non-existent-id"); + }); }); });