Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402",
Expand Down
16 changes: 10 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -179,20 +179,24 @@ export const buildCmd = (): CompletableCommand => {
.completer(optionsCompleter)
.description("Execute local YAML test cases")
.helpGroup("execute")
.requiredOption("-u, --url <url>", "url the tests should run against")
.requiredOption("-u, --url <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));
Expand Down
27 changes: 26 additions & 1 deletion src/debugtopus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
70 changes: 56 additions & 14 deletions tests/debugtopus/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<typeof existsSync>[0]) => {
const pathStr =
typeof pathThatShouldExist === "string"
? pathThatShouldExist
: pathThatShouldExist.toString();
return pathStr.includes("node_modules");
},
(pathThatShouldExist: Parameters<typeof existsSync>[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,
Expand All @@ -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",
Expand All @@ -269,6 +270,7 @@ describe("debugtopus", () => {
testTargetId: "test-target-id",
executionUrl: "https://example.com",
environmentId: undefined,
filterTestCaseIds: undefined,
},
parseAs: "stream",
}),
Expand All @@ -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");
});
});
});