From 04c59b36de15a88a7c9126bd86a8e3b0f4a304bd Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Mon, 12 Jan 2026 12:00:53 +0100 Subject: [PATCH 01/36] edit test cases --- package.json | 1 + src/cli.ts | 13 ++++ src/tools/sync/push.ts | 4 +- src/tools/yamlMutations/edit.ts | 113 ++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/tools/yamlMutations/edit.ts diff --git a/package.json b/package.json index f9521d6..0c31ce4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "gendoc": "tsx scripts/generate-docs.ts > README.md", "lint": "pnpm apigen && npx genversion -des src/version.ts && biome check", "tsc": "tsc --project tsconfig.build.json", + "dev:local": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.local.json OCTOMIND_API_URL=http://localhost:3000/api tsx src/index.ts", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.prod.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", diff --git a/src/cli.ts b/src/cli.ts index 59ddf3e..22213a4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,6 +38,7 @@ import { } from "./tools"; import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; +import { edit } from "./tools/yamlMutations/edit"; import { version } from "./version"; export const BINARY_NAME = "octomind"; @@ -412,6 +413,18 @@ export const buildCmd = (): CompletableCommand => { ) .action(addTestTargetWrapper(pushTestTarget)); + // noinspection RequiredAttributes + createCommandWithCommonOptions(program, "edit-test-case") + .completer(testTargetIdCompleter) + .description("Edit yaml test case") + .helpGroup("test-cases") + .addOption(testTargetIdOption) + .requiredOption( + "-f, --file-path ", + "The path to the local yaml file you want to edit", + ) + .action(addTestTargetWrapper(edit)); + createCommandWithCommonOptions(program, "list-test-targets") .description("List all test targets") .helpGroup("test-targets") diff --git a/src/tools/sync/push.ts b/src/tools/sync/push.ts index 647e01e..173f548 100644 --- a/src/tools/sync/push.ts +++ b/src/tools/sync/push.ts @@ -65,9 +65,9 @@ const defaultPush = async ( return data; }; -const draftPush = async ( +export const draftPush = async ( body: TestTargetSyncData, - options: PushOptions & ListOptions, + options: Omit & ListOptions, ): Promise<{ success: boolean; versionIds: string[] } | undefined> => { const { data, error } = await options.client.POST( "/apiKey/beta/test-targets/{testTargetId}/draft/push", diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts new file mode 100644 index 0000000..b5f667a --- /dev/null +++ b/src/tools/yamlMutations/edit.ts @@ -0,0 +1,113 @@ +import fs from "fs"; +import fsPromises from "fs/promises"; +import path from "path"; + +import { Client } from "openapi-fetch"; +import { ErrorResponse } from "openapi-typescript-helpers"; +import yaml from "yaml"; + +import { paths } from "../../api"; +import { client, handleError } from "../client"; +import { checkForConsistency } from "../sync/consistency"; +import { draftPush } from "../sync/push"; +import { SyncTestCase } from "../sync/types"; +import { readTestCasesFromDir } from "../sync/yml"; + +type EditOptions = { + testTargetId: string; + filePath: string; +}; + +const findOctomindFolder = async ( + startPath: string, +): Promise => { + let currentDir = path.dirname(startPath); + + while (currentDir !== path.parse(currentDir).root) { + const octomindPath = path.join(currentDir, ".octomind"); + if ( + fs.existsSync(octomindPath) && + (await fsPromises.stat(octomindPath)).isDirectory() + ) { + return octomindPath; + } + currentDir = path.dirname(currentDir); + } + + const rootOctomind = path.join(currentDir, ".octomind"); + if ( + fs.existsSync(rootOctomind) && + (await fsPromises.stat(rootOctomind)).isDirectory() + ) { + return rootOctomind; + } + + return null; +}; + +const getRelevantTestCases = ( + testCasesById: Record, + startTestCase: SyncTestCase, +): SyncTestCase[] => { + let dependencyId = startTestCase.dependencyId; + const result: SyncTestCase[] = [startTestCase]; + + while (dependencyId) { + const currentTestCase = testCasesById[dependencyId]; + + if (!currentTestCase) { + throw new Error( + `Could not find dependency ${dependencyId} for ${startTestCase.id}`, + ); + } + + result.push(currentTestCase); + dependencyId = currentTestCase?.dependencyId; + } + + return result; +}; + +const loadTestCase = (path: string): SyncTestCase => { + try { + const content = fs.readFileSync(path, "utf8"); + return yaml.parse(content); + } catch (error) { + throw new Error(`Could not parse ${path}: ${error}`); + } +}; + +export const edit = async (options: EditOptions): Promise => { + console.log(options) + const resolvedPath = path.resolve(options.filePath); + + const testCaseToEdit = loadTestCase(resolvedPath); + + const octomindRoot = await findOctomindFolder(options.filePath); + + if (!octomindRoot) { + throw new Error( + "Could not find .octomind folder, make sure to pull before trying to edit", + ); + } + + const testCases = readTestCasesFromDir(octomindRoot); + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); + + console.log(relevantTestCases); + checkForConsistency(relevantTestCases); + + const response = await draftPush( + { + testCases: relevantTestCases, + }, + { + testTargetId: options.testTargetId, + client, + onError: handleError, + }, + ); + + console.log(response); +}; From 9f75a385c9137ffbe24158d2b7ec69e107f65301 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Mon, 12 Jan 2026 13:54:49 +0100 Subject: [PATCH 02/36] remove logs --- src/tools/yamlMutations/edit.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b5f667a..ba9dce5 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -2,11 +2,8 @@ import fs from "fs"; import fsPromises from "fs/promises"; import path from "path"; -import { Client } from "openapi-fetch"; -import { ErrorResponse } from "openapi-typescript-helpers"; import yaml from "yaml"; -import { paths } from "../../api"; import { client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -68,17 +65,16 @@ const getRelevantTestCases = ( return result; }; -const loadTestCase = (path: string): SyncTestCase => { +const loadTestCase = (testCasePath: string): SyncTestCase => { try { - const content = fs.readFileSync(path, "utf8"); + const content = fs.readFileSync(testCasePath, "utf8"); return yaml.parse(content); } catch (error) { - throw new Error(`Could not parse ${path}: ${error}`); + throw new Error(`Could not parse ${testCasePath}: ${error}`); } }; export const edit = async (options: EditOptions): Promise => { - console.log(options) const resolvedPath = path.resolve(options.filePath); const testCaseToEdit = loadTestCase(resolvedPath); @@ -95,7 +91,6 @@ export const edit = async (options: EditOptions): Promise => { const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); - console.log(relevantTestCases); checkForConsistency(relevantTestCases); const response = await draftPush( From 9a96e3db81872d4f329c44549feb1c8a31c9d86d Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 14:04:00 +0100 Subject: [PATCH 03/36] folder name constant --- src/cli.ts | 7 ++++--- src/constants.ts | 1 + src/tools/yamlMutations/edit.ts | 10 ++++------ 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 src/constants.ts diff --git a/src/cli.ts b/src/cli.ts index 22213a4..800fa72 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -40,6 +40,7 @@ import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; import { edit } from "./tools/yamlMutations/edit"; import { version } from "./version"; +import { OCTOMIND_FOLDER_NAME } from "./constants"; export const BINARY_NAME = "octomind"; @@ -197,7 +198,7 @@ export const buildCmd = (): CompletableCommand => { .option( "-s, --source ", "Source directory (defaults to current directory)", - "./.octomind", + OCTOMIND_FOLDER_NAME, ) .action(addTestTargetWrapper(executeLocalTestCases)); @@ -397,7 +398,7 @@ export const buildCmd = (): CompletableCommand => { .description("Pull test cases from the test target") .helpGroup("test-cases") .addOption(testTargetIdOption) - .option("-d, --destination ", "Destination folder", "./.octomind") + .option("-d, --destination ", "Destination folder", OCTOMIND_FOLDER_NAME) .action(addTestTargetWrapper(pullTestTarget)); // noinspection RequiredAttributes @@ -409,7 +410,7 @@ export const buildCmd = (): CompletableCommand => { .option( "-s, --source ", "Source directory (defaults to current directory)", - ".octomind", + OCTOMIND_FOLDER_NAME, ) .action(addTestTargetWrapper(pushTestTarget)); diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..0ec372d --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const OCTOMIND_FOLDER_NAME = ".octomind" diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b5f667a..623180d 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -2,20 +2,19 @@ import fs from "fs"; import fsPromises from "fs/promises"; import path from "path"; -import { Client } from "openapi-fetch"; -import { ErrorResponse } from "openapi-typescript-helpers"; import yaml from "yaml"; -import { paths } from "../../api"; import { client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir } from "../sync/yml"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; type EditOptions = { testTargetId: string; filePath: string; + sourceDir: string }; const findOctomindFolder = async ( @@ -24,7 +23,7 @@ const findOctomindFolder = async ( let currentDir = path.dirname(startPath); while (currentDir !== path.parse(currentDir).root) { - const octomindPath = path.join(currentDir, ".octomind"); + const octomindPath = path.join(currentDir, OCTOMIND_FOLDER_NAME); if ( fs.existsSync(octomindPath) && (await fsPromises.stat(octomindPath)).isDirectory() @@ -34,7 +33,7 @@ const findOctomindFolder = async ( currentDir = path.dirname(currentDir); } - const rootOctomind = path.join(currentDir, ".octomind"); + const rootOctomind = path.join(currentDir, OCTOMIND_FOLDER_NAME); if ( fs.existsSync(rootOctomind) && (await fsPromises.stat(rootOctomind)).isDirectory() @@ -78,7 +77,6 @@ const loadTestCase = (path: string): SyncTestCase => { }; export const edit = async (options: EditOptions): Promise => { - console.log(options) const resolvedPath = path.resolve(options.filePath); const testCaseToEdit = loadTestCase(resolvedPath); From 2114e3e99fedee6e66dd90f639c6aab1e1c74ca5 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 15:28:20 +0100 Subject: [PATCH 04/36] make .octomind folder the default for push, pull, delete and edit. --- src/cli.ts | 12 -------- src/helpers.ts | 43 +++++++++++++++++++++++++++ src/tools/test-cases.ts | 52 +++++++++++++++++++++++---------- src/tools/test-targets.ts | 16 ++++++---- src/tools/yamlMutations/edit.ts | 43 +++++---------------------- 5 files changed, 98 insertions(+), 68 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 800fa72..ed6e970 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -40,7 +40,6 @@ import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; import { edit } from "./tools/yamlMutations/edit"; import { version } from "./version"; -import { OCTOMIND_FOLDER_NAME } from "./constants"; export const BINARY_NAME = "octomind"; @@ -195,11 +194,6 @@ export const buildCmd = (): CompletableCommand => { .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") - .option( - "-s, --source ", - "Source directory (defaults to current directory)", - OCTOMIND_FOLDER_NAME, - ) .action(addTestTargetWrapper(executeLocalTestCases)); createCommandWithCommonOptions(program, "test-report") @@ -398,7 +392,6 @@ export const buildCmd = (): CompletableCommand => { .description("Pull test cases from the test target") .helpGroup("test-cases") .addOption(testTargetIdOption) - .option("-d, --destination ", "Destination folder", OCTOMIND_FOLDER_NAME) .action(addTestTargetWrapper(pullTestTarget)); // noinspection RequiredAttributes @@ -407,11 +400,6 @@ export const buildCmd = (): CompletableCommand => { .description("Push local YAML test cases to the test target") .helpGroup("test-cases") .addOption(testTargetIdOption) - .option( - "-s, --source ", - "Source directory (defaults to current directory)", - OCTOMIND_FOLDER_NAME, - ) .action(addTestTargetWrapper(pushTestTarget)); // noinspection RequiredAttributes diff --git a/src/helpers.ts b/src/helpers.ts index 7980a26..1898578 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -2,6 +2,10 @@ import { stdin as input, stdout as output } from "node:process"; import { createInterface } from "node:readline"; import { loadConfig } from "./config"; +import path from "node:path"; +import fsPromises from "fs/promises"; +import { OCTOMIND_FOLDER_NAME } from "./constants"; + export function promptUser(question: string): Promise { const rl = createInterface({ input, output }); @@ -29,3 +33,42 @@ export const resolveTestTargetId = async ( } return config.testTargetId; }; + + +const isDirectory = async (dirPath: string): Promise => { + try { + const stat = await fsPromises.stat(dirPath); + return stat.isDirectory(); + } catch { + return false; + } +}; + +export const findOctomindFolder = async (): Promise => { + let currentDir = process.cwd(); + + while (currentDir !== path.parse(currentDir).root) { + const octomindPath = path.join(currentDir, OCTOMIND_FOLDER_NAME); + if (await isDirectory(octomindPath)) { + return octomindPath; + } + currentDir = path.dirname(currentDir); + } + + const rootOctomind = path.join(currentDir, OCTOMIND_FOLDER_NAME); + if (await isDirectory(rootOctomind)) { + return rootOctomind; + } + + return null +}; + +export const getAbsoluteFilePathInOctomindRoot = ({ filePath, octomindRoot }: { filePath: string, octomindRoot: string }): string | null => { + let resolvedPath: string + if (path.isAbsolute(filePath)) { + resolvedPath = filePath + } + resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)); + + return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null +} diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts index 1949723..cb54a44 100644 --- a/src/tools/test-cases.ts +++ b/src/tools/test-cases.ts @@ -1,7 +1,11 @@ +import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript +import { findOctomindFolder } from "../helpers"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; +import { buildFilename, readTestCasesFromDir } from "./sync/yml"; +import { unlink } from "fs/promises"; export type TestCaseResponse = components["schemas"]["TestCaseResponse"]; export type TestCasesResponse = components["schemas"]["TestCasesResponse"]; @@ -13,26 +17,44 @@ export type DeleteTestCaseParams = export const deleteTestCase = async ( options: DeleteTestCaseParams & ListOptions, ): Promise => { - const { data, error } = await client.DELETE( - "/apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}", - { - params: { - path: { - testTargetId: options.testTargetId, - testCaseId: options.testCaseId, + const octomindRoot = await findOctomindFolder() + + if (!octomindRoot) { + const { data, error } = await client.DELETE( + "/apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}", + { + params: { + path: { + testTargetId: options.testTargetId, + testCaseId: options.testCaseId, + }, }, }, - }, - ); - - handleError(error); + ); - if (options.json) { - logJson(data); - return; + handleError(error); + if (options.json) { + logJson(data); + } + console.log("Test Case deleted successfully"); + } else { + const testCases = readTestCasesFromDir(octomindRoot); + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + const existingTestCase = testCasesById[options.testCaseId] + + if (existingTestCase) { + const existingTestCasePath = buildFilename( + existingTestCase, + octomindRoot, + ); + await unlink(path.join(octomindRoot, existingTestCasePath)) + console.log("Test Case deleted successfully"); + } + else { + console.log(`No test case with id ${options.testCaseId} found in folder ${octomindRoot}`) + } } - console.log("Test Case deleted successfully"); }; export const listTestCase = async ( diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index 112a2d2..0b2238f 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -4,6 +4,8 @@ import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; import { writeYaml } from "./sync/yml"; +import { findOctomindFolder } from "../helpers"; +import { OCTOMIND_FOLDER_NAME } from "../constants"; export const getTestTargets = async () => { const { data, error } = await client.GET("/apiKey/v3/test-targets"); @@ -44,7 +46,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { }; export const pullTestTarget = async ( - options: { testTargetId: string; destination?: string } & ListOptions, + options: { testTargetId: string } & ListOptions, ): Promise => { const { data, error } = await client.GET( "/apiKey/beta/test-targets/{testTargetId}/pull", @@ -68,17 +70,19 @@ export const pullTestTarget = async ( return; } - writeYaml(data, options.destination); + const destination = await findOctomindFolder() ?? path.join(process.cwd(), OCTOMIND_FOLDER_NAME) + writeYaml(data, destination); console.log("Test Target pulled successfully"); }; export const pushTestTarget = async ( - options: { testTargetId: string; source?: string } & ListOptions, + options: { testTargetId: string } & ListOptions, ): Promise => { - const sourceDir = options.source - ? path.resolve(options.source) - : process.cwd(); + const sourceDir = await findOctomindFolder() + if (!sourceDir) { + throw new Error(`No ${OCTOMIND_FOLDER_NAME} folder found, please pull first.`) + } const data = await push({ ...options, diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 953dfde..114f09d 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,5 +1,4 @@ import fs from "fs"; -import fsPromises from "fs/promises"; import path from "path"; import yaml from "yaml"; @@ -9,40 +8,13 @@ import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir } from "../sync/yml"; -import { OCTOMIND_FOLDER_NAME } from "../../constants"; +import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; type EditOptions = { testTargetId: string; filePath: string; - sourceDir: string }; -const findOctomindFolder = async ( - startPath: string, -): Promise => { - let currentDir = path.dirname(startPath); - - while (currentDir !== path.parse(currentDir).root) { - const octomindPath = path.join(currentDir, OCTOMIND_FOLDER_NAME); - if ( - fs.existsSync(octomindPath) && - (await fsPromises.stat(octomindPath)).isDirectory() - ) { - return octomindPath; - } - currentDir = path.dirname(currentDir); - } - - const rootOctomind = path.join(currentDir, OCTOMIND_FOLDER_NAME); - if ( - fs.existsSync(rootOctomind) && - (await fsPromises.stat(rootOctomind)).isDirectory() - ) { - return rootOctomind; - } - - return null; -}; const getRelevantTestCases = ( testCasesById: Record, @@ -77,17 +49,18 @@ const loadTestCase = (testCasePath: string): SyncTestCase => { }; export const edit = async (options: EditOptions): Promise => { - const resolvedPath = path.resolve(options.filePath); - - const testCaseToEdit = loadTestCase(resolvedPath); - - const octomindRoot = await findOctomindFolder(options.filePath); - + const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { throw new Error( "Could not find .octomind folder, make sure to pull before trying to edit", ); } + const testCaseFilePath = getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + if (!testCaseFilePath) { + throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) + } + + const testCaseToEdit = loadTestCase(testCaseFilePath); const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); From d33c197207e4b34da7a02ef85016ae7c72c6212a Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 15:43:06 +0100 Subject: [PATCH 05/36] use octomind folder in local execution --- src/debugtopus/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index c8de521..4631c64 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -19,6 +19,7 @@ import { import { client, handleError } from "../tools/client"; import { readTestCasesFromDir } from "../tools/sync/yml"; import { ensureChromiumIsInstalled } from "./installation"; +import { findOctomindFolder } from "../helpers"; export type DebugtopusOptions = { testCaseId?: string; @@ -207,8 +208,8 @@ export const runDebugtopus = async (options: DebugtopusOptions) => { .filter((testCase) => options.grep ? testCase.description - ?.toLowerCase() - .includes(options.grep.toLowerCase()) + ?.toLowerCase() + .includes(options.grep.toLowerCase()) : true, ) .map(async (testCase) => ({ @@ -262,9 +263,16 @@ export const runDebugtopus = async (options: DebugtopusOptions) => { }; export const executeLocalTestCases = async ( - options: DebugtopusOptions & { source: string }, + options: DebugtopusOptions, ): Promise => { - const testCases = readTestCasesFromDir(options.source); + const octomindRoot = await findOctomindFolder() + if (!octomindRoot) { + throw new Error( + "Could not find .octomind folder, make sure to pull before trying to edit", + ); + } + + const testCases = readTestCasesFromDir(octomindRoot); const body = { testCases, testTargetId: options.testTargetId, From cd8046765e814c583511e8b9b6b7e66d07394fa6 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 15:59:51 +0100 Subject: [PATCH 06/36] fix .octomind folder name in logs --- src/debugtopus/index.ts | 3 ++- src/tools/yamlMutations/edit.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 4631c64..23bae21 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -20,6 +20,7 @@ import { client, handleError } from "../tools/client"; import { readTestCasesFromDir } from "../tools/sync/yml"; import { ensureChromiumIsInstalled } from "./installation"; import { findOctomindFolder } from "../helpers"; +import { OCTOMIND_FOLDER_NAME } from "../constants"; export type DebugtopusOptions = { testCaseId?: string; @@ -268,7 +269,7 @@ export const executeLocalTestCases = async ( const octomindRoot = await findOctomindFolder() if (!octomindRoot) { throw new Error( - "Could not find .octomind folder, make sure to pull before trying to edit", + `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to execute locally`, ); } diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 114f09d..c612827 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -9,6 +9,7 @@ import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir } from "../sync/yml"; import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; type EditOptions = { testTargetId: string; @@ -52,7 +53,7 @@ export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { throw new Error( - "Could not find .octomind folder, make sure to pull before trying to edit", + `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } const testCaseFilePath = getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) From 8f6ada4d0f3d2fe4f180426d93a0ad7a54a374f9 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 16:39:18 +0100 Subject: [PATCH 07/36] fix octomind folder helpers and add tests. --- src/helpers.ts | 15 +-- src/tools/yamlMutations/edit.ts | 2 +- tests/helpers.spec.ts | 186 ++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 tests/helpers.spec.ts diff --git a/src/helpers.ts b/src/helpers.ts index 1898578..59fd43f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -63,12 +63,13 @@ export const findOctomindFolder = async (): Promise => { return null }; -export const getAbsoluteFilePathInOctomindRoot = ({ filePath, octomindRoot }: { filePath: string, octomindRoot: string }): string | null => { - let resolvedPath: string - if (path.isAbsolute(filePath)) { - resolvedPath = filePath +export const getAbsoluteFilePathInOctomindRoot = async ({ filePath, octomindRoot }: { filePath: string, octomindRoot: string }): Promise => { + try { + const resolvedPath = await fsPromises.realpath( + path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath) + ); + return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; + } catch { + return null; } - resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)); - - return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null } diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index c612827..09ee3e5 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -56,7 +56,7 @@ export const edit = async (options: EditOptions): Promise => { `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } - const testCaseFilePath = getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) if (!testCaseFilePath) { throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) } diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts new file mode 100644 index 0000000..2b28b27 --- /dev/null +++ b/tests/helpers.spec.ts @@ -0,0 +1,186 @@ +import fsPromises from "fs/promises"; +import os from "os"; +import path from "path"; + +import { + findOctomindFolder, + getAbsoluteFilePathInOctomindRoot, +} from "../src/helpers"; +import { OCTOMIND_FOLDER_NAME } from "../src/constants"; + +describe("helpers", () => { + describe("findOctomindFolder", () => { + let tmpDir: string; + const originalCwd = process.cwd; + + beforeEach(async () => { + tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "octomind-test-")); + }); + + afterEach(async () => { + process.cwd = originalCwd; + await fsPromises.rm(tmpDir, { recursive: true }); + }); + + it("should find .octomind folder in current directory", async () => { + const octomindPath = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + await fsPromises.mkdir(octomindPath); + process.cwd = () => tmpDir; + + const result = await findOctomindFolder(); + + expect(result).toBe(octomindPath); + }); + + it("should find .octomind folder in parent directory", async () => { + const octomindPath = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + const subdir = path.join(tmpDir, "subdir"); + await fsPromises.mkdir(octomindPath); + await fsPromises.mkdir(subdir); + process.cwd = () => subdir; + + const result = await findOctomindFolder(); + + expect(result).toBe(octomindPath); + }); + + it("should traverse multiple levels to find .octomind folder", async () => { + const octomindPath = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + const deepDir = path.join(tmpDir, "a", "b", "c"); + await fsPromises.mkdir(octomindPath); + await fsPromises.mkdir(deepDir, { recursive: true }); + process.cwd = () => deepDir; + + const result = await findOctomindFolder(); + + expect(result).toBe(octomindPath); + }); + + it("should return null when .octomind folder does not exist", async () => { + process.cwd = () => tmpDir; + + const result = await findOctomindFolder(); + + expect(result).toBeNull(); + }); + + it("should return null when .octomind is a file instead of directory", async () => { + const octomindPath = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + await fsPromises.writeFile(octomindPath, ""); + process.cwd = () => tmpDir; + + const result = await findOctomindFolder(); + + expect(result).toBeNull(); + }); + + it("should find .octomind folder when inside a subfolder of it", async () => { + const octomindPath = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + const subfolderInOctomind = path.join(octomindPath, "subfolder"); + await fsPromises.mkdir(subfolderInOctomind, { recursive: true }); + process.cwd = () => subfolderInOctomind; + + const result = await findOctomindFolder(); + + expect(result).toBe(octomindPath); + }); + }); + + describe("getAbsoluteFilePathInOctomindRoot", () => { + let tmpDir: string; + let octomindRoot: string; + + beforeEach(async () => { + tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "octomind-test-")); + octomindRoot = path.join(tmpDir, OCTOMIND_FOLDER_NAME); + await fsPromises.mkdir(octomindRoot); + }); + + afterEach(async () => { + await fsPromises.rm(tmpDir, { recursive: true }); + }); + + it("should resolve relative path within octomind root", async () => { + const filePath = path.join(octomindRoot, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "test-case.yaml", + octomindRoot, + }); + + expect(result).toBe(filePath); + }); + + it("should resolve nested relative path within octomind root", async () => { + const subdir = path.join(octomindRoot, "subdir"); + await fsPromises.mkdir(subdir); + const filePath = path.join(subdir, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "subdir/test-case.yaml", + octomindRoot, + }); + + expect(result).toBe(filePath); + }); + + it("should accept absolute path within octomind root", async () => { + const filePath = path.join(octomindRoot, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath, + octomindRoot, + }); + + expect(result).toBe(filePath); + }); + + it("should return null for absolute path outside octomind root", async () => { + const outsideFile = path.join(tmpDir, "outside.yaml"); + await fsPromises.writeFile(outsideFile, ""); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: outsideFile, + octomindRoot, + }); + + expect(result).toBeNull(); + }); + + it("should return null for path traversal attempts", async () => { + const outsideFile = path.join(tmpDir, "outside.yaml"); + await fsPromises.writeFile(outsideFile, ""); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "../outside.yaml", + octomindRoot, + }); + + expect(result).toBeNull(); + }); + + it("should return null for non-existent file", async () => { + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "does-not-exist.yaml", + octomindRoot, + }); + + expect(result).toBeNull(); + }); + + it("should return null for malformed path with nested octomind structure", async () => { + // e.g. /some/folder/.octomind/some/folder/.octomind/a.yaml + const malformedPath = path.join(octomindRoot, tmpDir, OCTOMIND_FOLDER_NAME, "a.yaml"); + + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: malformedPath, + octomindRoot, + }); + + expect(result).toBeNull(); + }); + }); +}); From afb21b5801abf2048021e70f7aa096189114ed8f Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:04:56 +0100 Subject: [PATCH 08/36] use early exits in deleteTestCase and add tests for local deletion. --- src/tools/test-cases.ts | 34 +++++------ tests/tools/test-cases.spec.ts | 103 +++++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 34 deletions(-) diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts index cb54a44..ab41e77 100644 --- a/src/tools/test-cases.ts +++ b/src/tools/test-cases.ts @@ -5,7 +5,7 @@ import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; import { buildFilename, readTestCasesFromDir } from "./sync/yml"; -import { unlink } from "fs/promises"; +import fsPromises from "fs/promises"; export type TestCaseResponse = components["schemas"]["TestCaseResponse"]; export type TestCasesResponse = components["schemas"]["TestCasesResponse"]; @@ -37,24 +37,24 @@ export const deleteTestCase = async ( logJson(data); } console.log("Test Case deleted successfully"); - } else { - const testCases = readTestCasesFromDir(octomindRoot); - const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); - const existingTestCase = testCasesById[options.testCaseId] - - if (existingTestCase) { - const existingTestCasePath = buildFilename( - existingTestCase, - octomindRoot, - ); - await unlink(path.join(octomindRoot, existingTestCasePath)) - console.log("Test Case deleted successfully"); - } - else { - console.log(`No test case with id ${options.testCaseId} found in folder ${octomindRoot}`) - } + return + } + + const testCases = readTestCasesFromDir(octomindRoot); + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + const existingTestCase = testCasesById[options.testCaseId] + + if (!existingTestCase) { + console.log(`No test case with id ${options.testCaseId} found in folder ${octomindRoot}`) + return } + const existingTestCasePath = buildFilename( + existingTestCase, + octomindRoot, + ); + await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)) + console.log("Test Case deleted successfully"); }; export const listTestCase = async ( diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 3f1d147..5ccac83 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -1,38 +1,107 @@ +import fsPromises from "fs/promises"; +import os from "os"; +import path from "path"; + import { client, handleError } from "../../src/tools/client"; +import { findOctomindFolder } from "../../src/helpers"; +import { readTestCasesFromDir, buildFilename } from "../../src/tools/sync/yml"; import { deleteTestCase } from "../../src/tools/test-cases"; jest.mock("../../src/tools/client"); +jest.mock("../../src/helpers"); +jest.mock("../../src/tools/sync/yml"); describe("test-cases", () => { + const originalConsoleLog = console.log; let clientDELETE: jest.Mock; + beforeEach(() => { jest.clearAllMocks(); clientDELETE = client.DELETE as jest.Mock; console.log = jest.fn(); }); - it("should delete a test case", async () => { - clientDELETE.mockResolvedValue({ - data: { success: true }, - error: undefined, + afterEach(() => { + console.log = originalConsoleLog; + }); + + describe("deleteTestCase via API", () => { + beforeEach(() => { + jest.mocked(findOctomindFolder).mockResolvedValue(null); }); - await deleteTestCase({ - testTargetId: "test-target-id", - testCaseId: "test-case-id", + + it("should delete a test case", async () => { + clientDELETE.mockResolvedValue({ + data: { success: true }, + error: undefined, + }); + await deleteTestCase({ + testTargetId: "test-target-id", + testCaseId: "test-case-id", + }); + expect(handleError).toHaveBeenCalledWith(undefined); + expect(console.log).toHaveBeenCalledWith("Test Case deleted successfully"); + }); + + it("should handle error", async () => { + clientDELETE.mockResolvedValue({ + data: undefined, + error: { message: "error" }, + }); + await deleteTestCase({ + testTargetId: "test-target-id", + testCaseId: "test-case-id", + }); + expect(handleError).toHaveBeenCalledWith({ message: "error" }); }); - expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith("Test Case deleted successfully"); }); - it("should handle error", async () => { - clientDELETE.mockResolvedValue({ - data: undefined, - error: { message: "error" }, + describe("deleteTestCase via local filesystem", () => { + let tmpDir: string; + + beforeEach(async () => { + tmpDir = await fsPromises.mkdtemp( + path.join(os.tmpdir(), "octomind-test-"), + ); + jest.mocked(findOctomindFolder).mockResolvedValue(tmpDir); + }); + + afterEach(async () => { + await fsPromises.rm(tmpDir, { recursive: true }); }); - await deleteTestCase({ - testTargetId: "test-target-id", - testCaseId: "test-case-id", + + it("should delete a test case file when it exists locally", async () => { + const testCaseId = "test-case-id"; + const fileName = "myTestCase.yaml"; + const filePath = path.join(tmpDir, fileName); + await fsPromises.writeFile(filePath, ""); + + jest.mocked(readTestCasesFromDir).mockReturnValue([ + { id: testCaseId, description: "My Test Case" }, + ] as ReturnType); + jest.mocked(buildFilename).mockReturnValue(fileName); + + await deleteTestCase({ + testTargetId: "test-target-id", + testCaseId, + }); + + expect(console.log).toHaveBeenCalledWith("Test Case deleted successfully"); + await expect(fsPromises.access(filePath)).rejects.toThrow(); + }); + + it("should log message when test case ID is not found locally", async () => { + jest.mocked(readTestCasesFromDir).mockReturnValue([]); + + await deleteTestCase({ + testTargetId: "test-target-id", + testCaseId: "non-existent-id", + }); + + expect(console.log).toHaveBeenCalledWith( + `No test case with id non-existent-id found in folder ${tmpDir}`, + ); + expect(clientDELETE).not.toHaveBeenCalled(); }); - expect(handleError).toHaveBeenCalledWith({ message: "error" }); }); }); From d316985fcf1b7321bb84cb1d519b20eb44fbe36b Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:30:42 +0100 Subject: [PATCH 09/36] remove edit feature. --- src/cli.ts | 13 ---- src/tools/yamlMutations/edit.ts | 84 -------------------- tests/tools/yamlMutation/edit.spec.ts | 106 ++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 97 deletions(-) delete mode 100644 src/tools/yamlMutations/edit.ts create mode 100644 tests/tools/yamlMutation/edit.spec.ts diff --git a/src/cli.ts b/src/cli.ts index ed6e970..3ffc061 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,7 +38,6 @@ import { } from "./tools"; import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; -import { edit } from "./tools/yamlMutations/edit"; import { version } from "./version"; export const BINARY_NAME = "octomind"; @@ -402,18 +401,6 @@ export const buildCmd = (): CompletableCommand => { .addOption(testTargetIdOption) .action(addTestTargetWrapper(pushTestTarget)); - // noinspection RequiredAttributes - createCommandWithCommonOptions(program, "edit-test-case") - .completer(testTargetIdCompleter) - .description("Edit yaml test case") - .helpGroup("test-cases") - .addOption(testTargetIdOption) - .requiredOption( - "-f, --file-path ", - "The path to the local yaml file you want to edit", - ) - .action(addTestTargetWrapper(edit)); - createCommandWithCommonOptions(program, "list-test-targets") .description("List all test targets") .helpGroup("test-targets") diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts deleted file mode 100644 index 09ee3e5..0000000 --- a/src/tools/yamlMutations/edit.ts +++ /dev/null @@ -1,84 +0,0 @@ -import fs from "fs"; -import path from "path"; - -import yaml from "yaml"; - -import { client, handleError } from "../client"; -import { checkForConsistency } from "../sync/consistency"; -import { draftPush } from "../sync/push"; -import { SyncTestCase } from "../sync/types"; -import { readTestCasesFromDir } from "../sync/yml"; -import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; -import { OCTOMIND_FOLDER_NAME } from "../../constants"; - -type EditOptions = { - testTargetId: string; - filePath: string; -}; - - -const getRelevantTestCases = ( - testCasesById: Record, - startTestCase: SyncTestCase, -): SyncTestCase[] => { - let dependencyId = startTestCase.dependencyId; - const result: SyncTestCase[] = [startTestCase]; - - while (dependencyId) { - const currentTestCase = testCasesById[dependencyId]; - - if (!currentTestCase) { - throw new Error( - `Could not find dependency ${dependencyId} for ${startTestCase.id}`, - ); - } - - result.push(currentTestCase); - dependencyId = currentTestCase?.dependencyId; - } - - return result; -}; - -const loadTestCase = (testCasePath: string): SyncTestCase => { - try { - const content = fs.readFileSync(testCasePath, "utf8"); - return yaml.parse(content); - } catch (error) { - throw new Error(`Could not parse ${testCasePath}: ${error}`); - } -}; - -export const edit = async (options: EditOptions): Promise => { - const octomindRoot = await findOctomindFolder(); - if (!octomindRoot) { - throw new Error( - `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, - ); - } - const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) - if (!testCaseFilePath) { - throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) - } - - const testCaseToEdit = loadTestCase(testCaseFilePath); - - const testCases = readTestCasesFromDir(octomindRoot); - const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); - const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); - - checkForConsistency(relevantTestCases); - - const response = await draftPush( - { - testCases: relevantTestCases, - }, - { - testTargetId: options.testTargetId, - client, - onError: handleError, - }, - ); - - console.log(response); -}; diff --git a/tests/tools/yamlMutation/edit.spec.ts b/tests/tools/yamlMutation/edit.spec.ts new file mode 100644 index 0000000..f8b7703 --- /dev/null +++ b/tests/tools/yamlMutation/edit.spec.ts @@ -0,0 +1,106 @@ +import fs from "fs"; + +import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../../src/helpers"; +import { readTestCasesFromDir } from "../../../src/tools/sync/yml"; +import { checkForConsistency } from "../../../src/tools/sync/consistency"; +import { draftPush } from "../../../src/tools/sync/push"; +import { edit } from "../../../src/tools/yamlMutations/edit"; +import { OCTOMIND_FOLDER_NAME } from "../../../src/constants"; + +jest.mock("../../../src/helpers"); +jest.mock("../../../src/tools/sync/yml"); +jest.mock("../../../src/tools/sync/consistency"); +jest.mock("../../../src/tools/sync/push"); +jest.mock("fs"); + +const OCTOMIND_ROOT = "/project/.octomind"; +const TEST_CASE_PATH = `${OCTOMIND_ROOT}/myTestCase.yaml`; + +describe("edit", () => { + const originalConsoleLog = console.log; + + beforeEach(() => { + jest.clearAllMocks(); + console.log = jest.fn(); + + // Default happy path mocks + jest.mocked(findOctomindFolder).mockResolvedValue(OCTOMIND_ROOT); + jest.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue(TEST_CASE_PATH); + jest.mocked(fs.readFileSync).mockReturnValue(""); + jest.mocked(readTestCasesFromDir).mockReturnValue([]); + jest.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [] }); + }); + + afterEach(() => { + console.log = originalConsoleLog; + }); + + it("should throw error if octomind folder not found", async () => { + jest.mocked(findOctomindFolder).mockResolvedValue(null); + + await expect( + edit({ testTargetId: "test-target-id", filePath: "test.yaml" }), + ).rejects.toThrow( + `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, + ); + }); + + it("should throw error if file path not found in octomind root", async () => { + jest.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue(null); + + await expect( + edit({ testTargetId: "test-target-id", filePath: "nonexistent.yaml" }), + ).rejects.toThrow( + `Could not find nonexistent.yaml in folder ${OCTOMIND_ROOT}`, + ); + }); + + it("should load test case and call draftPush with relevant test cases", async () => { + const testCase = { id: "test-case-1", description: "My Test Case" }; + jest.mocked(fs.readFileSync).mockReturnValue("id: test-case-1\ndescription: My Test Case"); + jest.mocked(readTestCasesFromDir).mockReturnValue([testCase] as ReturnType); + + await edit({ testTargetId: "test-target-id", filePath: "myTestCase.yaml" }); + + expect(checkForConsistency).toHaveBeenCalledWith([testCase]); + expect(draftPush).toHaveBeenCalledWith( + { testCases: [testCase] }, + expect.objectContaining({ testTargetId: "test-target-id" }), + ); + }); + + it("should include dependency chain in relevant test cases", async () => { + const parentTestCase = { id: "parent-id", description: "Parent" }; + const childTestCase = { id: "child-id", description: "Child", dependencyId: "parent-id" }; + jest.mocked(fs.readFileSync).mockReturnValue("id: child-id\ndescription: Child\ndependencyId: parent-id"); + jest.mocked(readTestCasesFromDir).mockReturnValue([parentTestCase, childTestCase] as ReturnType); + + await edit({ testTargetId: "test-target-id", filePath: "childTest.yaml" }); + + expect(checkForConsistency).toHaveBeenCalledWith([childTestCase, parentTestCase]); + expect(draftPush).toHaveBeenCalledWith( + { testCases: [childTestCase, parentTestCase] }, + expect.objectContaining({ testTargetId: "test-target-id" }), + ); + }); + + it("should throw error if dependency not found", async () => { + const orphanTestCase = { id: "orphan-id", description: "Orphan", dependencyId: "missing-id" }; + jest.mocked(fs.readFileSync).mockReturnValue("id: orphan-id\ndescription: Orphan\ndependencyId: missing-id"); + jest.mocked(readTestCasesFromDir).mockReturnValue([orphanTestCase] as ReturnType); + + await expect( + edit({ testTargetId: "test-target-id", filePath: "orphan.yaml" }), + ).rejects.toThrow("Could not find dependency missing-id for orphan-id"); + }); + + it("should throw error if test case file cannot be parsed", async () => { + jest.mocked(fs.readFileSync).mockImplementation(() => { + throw new Error("File not found"); + }); + + await expect( + edit({ testTargetId: "test-target-id", filePath: "invalid.yaml" }), + ).rejects.toThrow(`Could not parse ${TEST_CASE_PATH}`); + }); +}); From 2c0507e4e04faa82a25c1930e11d3831322031ff Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:33:46 +0100 Subject: [PATCH 10/36] remove edit tests --- tests/tools/yamlMutation/edit.spec.ts | 106 -------------------------- 1 file changed, 106 deletions(-) delete mode 100644 tests/tools/yamlMutation/edit.spec.ts diff --git a/tests/tools/yamlMutation/edit.spec.ts b/tests/tools/yamlMutation/edit.spec.ts deleted file mode 100644 index f8b7703..0000000 --- a/tests/tools/yamlMutation/edit.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import fs from "fs"; - -import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../../src/helpers"; -import { readTestCasesFromDir } from "../../../src/tools/sync/yml"; -import { checkForConsistency } from "../../../src/tools/sync/consistency"; -import { draftPush } from "../../../src/tools/sync/push"; -import { edit } from "../../../src/tools/yamlMutations/edit"; -import { OCTOMIND_FOLDER_NAME } from "../../../src/constants"; - -jest.mock("../../../src/helpers"); -jest.mock("../../../src/tools/sync/yml"); -jest.mock("../../../src/tools/sync/consistency"); -jest.mock("../../../src/tools/sync/push"); -jest.mock("fs"); - -const OCTOMIND_ROOT = "/project/.octomind"; -const TEST_CASE_PATH = `${OCTOMIND_ROOT}/myTestCase.yaml`; - -describe("edit", () => { - const originalConsoleLog = console.log; - - beforeEach(() => { - jest.clearAllMocks(); - console.log = jest.fn(); - - // Default happy path mocks - jest.mocked(findOctomindFolder).mockResolvedValue(OCTOMIND_ROOT); - jest.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue(TEST_CASE_PATH); - jest.mocked(fs.readFileSync).mockReturnValue(""); - jest.mocked(readTestCasesFromDir).mockReturnValue([]); - jest.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [] }); - }); - - afterEach(() => { - console.log = originalConsoleLog; - }); - - it("should throw error if octomind folder not found", async () => { - jest.mocked(findOctomindFolder).mockResolvedValue(null); - - await expect( - edit({ testTargetId: "test-target-id", filePath: "test.yaml" }), - ).rejects.toThrow( - `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, - ); - }); - - it("should throw error if file path not found in octomind root", async () => { - jest.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue(null); - - await expect( - edit({ testTargetId: "test-target-id", filePath: "nonexistent.yaml" }), - ).rejects.toThrow( - `Could not find nonexistent.yaml in folder ${OCTOMIND_ROOT}`, - ); - }); - - it("should load test case and call draftPush with relevant test cases", async () => { - const testCase = { id: "test-case-1", description: "My Test Case" }; - jest.mocked(fs.readFileSync).mockReturnValue("id: test-case-1\ndescription: My Test Case"); - jest.mocked(readTestCasesFromDir).mockReturnValue([testCase] as ReturnType); - - await edit({ testTargetId: "test-target-id", filePath: "myTestCase.yaml" }); - - expect(checkForConsistency).toHaveBeenCalledWith([testCase]); - expect(draftPush).toHaveBeenCalledWith( - { testCases: [testCase] }, - expect.objectContaining({ testTargetId: "test-target-id" }), - ); - }); - - it("should include dependency chain in relevant test cases", async () => { - const parentTestCase = { id: "parent-id", description: "Parent" }; - const childTestCase = { id: "child-id", description: "Child", dependencyId: "parent-id" }; - jest.mocked(fs.readFileSync).mockReturnValue("id: child-id\ndescription: Child\ndependencyId: parent-id"); - jest.mocked(readTestCasesFromDir).mockReturnValue([parentTestCase, childTestCase] as ReturnType); - - await edit({ testTargetId: "test-target-id", filePath: "childTest.yaml" }); - - expect(checkForConsistency).toHaveBeenCalledWith([childTestCase, parentTestCase]); - expect(draftPush).toHaveBeenCalledWith( - { testCases: [childTestCase, parentTestCase] }, - expect.objectContaining({ testTargetId: "test-target-id" }), - ); - }); - - it("should throw error if dependency not found", async () => { - const orphanTestCase = { id: "orphan-id", description: "Orphan", dependencyId: "missing-id" }; - jest.mocked(fs.readFileSync).mockReturnValue("id: orphan-id\ndescription: Orphan\ndependencyId: missing-id"); - jest.mocked(readTestCasesFromDir).mockReturnValue([orphanTestCase] as ReturnType); - - await expect( - edit({ testTargetId: "test-target-id", filePath: "orphan.yaml" }), - ).rejects.toThrow("Could not find dependency missing-id for orphan-id"); - }); - - it("should throw error if test case file cannot be parsed", async () => { - jest.mocked(fs.readFileSync).mockImplementation(() => { - throw new Error("File not found"); - }); - - await expect( - edit({ testTargetId: "test-target-id", filePath: "invalid.yaml" }), - ).rejects.toThrow(`Could not parse ${TEST_CASE_PATH}`); - }); -}); From cd70a3fdff6e2f9bf865795dfa71c5c82f8b36f6 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:35:10 +0100 Subject: [PATCH 11/36] add edit CLI command --- src/cli.ts | 35 +++++++++----- src/tools/yamlMutations/edit.ts | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 src/tools/yamlMutations/edit.ts diff --git a/src/cli.ts b/src/cli.ts index 3ffc061..b94e207 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -39,6 +39,7 @@ import { import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; import { version } from "./version"; +import { edit } from "./tools/yamlMutations/edit"; export const BINARY_NAME = "octomind"; @@ -49,17 +50,17 @@ type WithTestTargetId = { testTargetId: string }; const addTestTargetWrapper = (fn: (options: T) => Promise) => - async ( - options: Omit & Partial>, - ): Promise => { - const resolvedTestTargetId = await resolveTestTargetId( - options.testTargetId, - ); - await fn({ - ...options, - testTargetId: resolvedTestTargetId, - } as T); - }; + async ( + options: Omit & Partial>, + ): Promise => { + const resolvedTestTargetId = await resolveTestTargetId( + options.testTargetId, + ); + await fn({ + ...options, + testTargetId: resolvedTestTargetId, + } as T); + }; const testTargetIdOption = new Option( "-t, --test-target-id [id]", @@ -401,6 +402,18 @@ export const buildCmd = (): CompletableCommand => { .addOption(testTargetIdOption) .action(addTestTargetWrapper(pushTestTarget)); + // noinspection RequiredAttributes + createCommandWithCommonOptions(program, "edit-test-case") + .completer(testTargetIdCompleter) + .description("Edit yaml test case") + .helpGroup("test-cases") + .addOption(testTargetIdOption) + .requiredOption( + "-f, --file-path ", + "The path to the local yaml file you want to edit", + ) + .action(addTestTargetWrapper(edit)); + createCommandWithCommonOptions(program, "list-test-targets") .description("List all test targets") .helpGroup("test-targets") diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts new file mode 100644 index 0000000..09ee3e5 --- /dev/null +++ b/src/tools/yamlMutations/edit.ts @@ -0,0 +1,84 @@ +import fs from "fs"; +import path from "path"; + +import yaml from "yaml"; + +import { client, handleError } from "../client"; +import { checkForConsistency } from "../sync/consistency"; +import { draftPush } from "../sync/push"; +import { SyncTestCase } from "../sync/types"; +import { readTestCasesFromDir } from "../sync/yml"; +import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; + +type EditOptions = { + testTargetId: string; + filePath: string; +}; + + +const getRelevantTestCases = ( + testCasesById: Record, + startTestCase: SyncTestCase, +): SyncTestCase[] => { + let dependencyId = startTestCase.dependencyId; + const result: SyncTestCase[] = [startTestCase]; + + while (dependencyId) { + const currentTestCase = testCasesById[dependencyId]; + + if (!currentTestCase) { + throw new Error( + `Could not find dependency ${dependencyId} for ${startTestCase.id}`, + ); + } + + result.push(currentTestCase); + dependencyId = currentTestCase?.dependencyId; + } + + return result; +}; + +const loadTestCase = (testCasePath: string): SyncTestCase => { + try { + const content = fs.readFileSync(testCasePath, "utf8"); + return yaml.parse(content); + } catch (error) { + throw new Error(`Could not parse ${testCasePath}: ${error}`); + } +}; + +export const edit = async (options: EditOptions): Promise => { + const octomindRoot = await findOctomindFolder(); + if (!octomindRoot) { + throw new Error( + `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, + ); + } + const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + if (!testCaseFilePath) { + throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) + } + + const testCaseToEdit = loadTestCase(testCaseFilePath); + + const testCases = readTestCasesFromDir(octomindRoot); + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); + + checkForConsistency(relevantTestCases); + + const response = await draftPush( + { + testCases: relevantTestCases, + }, + { + testTargetId: options.testTargetId, + client, + onError: handleError, + }, + ); + + console.log(response); +}; From 8ff8b31976adbd471537995a71c3ce5eb4ff2225 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:47:27 +0100 Subject: [PATCH 12/36] fix linting --- biome.json | 2 +- src/cli.ts | 24 ++++++++++++------------ src/constants.ts | 2 +- src/debugtopus/index.ts | 10 +++++----- src/helpers.ts | 20 ++++++++++++-------- src/tools/test-cases.ts | 22 +++++++++++----------- src/tools/test-targets.ts | 14 +++++++++----- src/tools/yamlMutations/edit.ts | 17 ++++++++++++----- tests/helpers.spec.ts | 17 +++++++++++++---- tests/tools/test-cases.spec.ts | 20 +++++++++++++------- 10 files changed, 89 insertions(+), 59 deletions(-) diff --git a/biome.json b/biome.json index 5ba9215..e30cda2 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.9/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", "vcs": { "enabled": false, "clientKind": "git", diff --git a/src/cli.ts b/src/cli.ts index b94e207..ed6e970 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,8 +38,8 @@ import { } from "./tools"; import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; -import { version } from "./version"; import { edit } from "./tools/yamlMutations/edit"; +import { version } from "./version"; export const BINARY_NAME = "octomind"; @@ -50,17 +50,17 @@ type WithTestTargetId = { testTargetId: string }; const addTestTargetWrapper = (fn: (options: T) => Promise) => - async ( - options: Omit & Partial>, - ): Promise => { - const resolvedTestTargetId = await resolveTestTargetId( - options.testTargetId, - ); - await fn({ - ...options, - testTargetId: resolvedTestTargetId, - } as T); - }; + async ( + options: Omit & Partial>, + ): Promise => { + const resolvedTestTargetId = await resolveTestTargetId( + options.testTargetId, + ); + await fn({ + ...options, + testTargetId: resolvedTestTargetId, + } as T); + }; const testTargetIdOption = new Option( "-t, --test-target-id [id]", diff --git a/src/constants.ts b/src/constants.ts index 0ec372d..5701db5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1 @@ -export const OCTOMIND_FOLDER_NAME = ".octomind" +export const OCTOMIND_FOLDER_NAME = ".octomind"; diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 23bae21..8450165 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -10,6 +10,8 @@ import { promisify } from "util"; import { Open } from "unzipper"; +import { OCTOMIND_FOLDER_NAME } from "../constants"; +import { findOctomindFolder } from "../helpers"; import { getEnvironments, getPlaywrightCode, @@ -19,8 +21,6 @@ import { import { client, handleError } from "../tools/client"; import { readTestCasesFromDir } from "../tools/sync/yml"; import { ensureChromiumIsInstalled } from "./installation"; -import { findOctomindFolder } from "../helpers"; -import { OCTOMIND_FOLDER_NAME } from "../constants"; export type DebugtopusOptions = { testCaseId?: string; @@ -209,8 +209,8 @@ export const runDebugtopus = async (options: DebugtopusOptions) => { .filter((testCase) => options.grep ? testCase.description - ?.toLowerCase() - .includes(options.grep.toLowerCase()) + ?.toLowerCase() + .includes(options.grep.toLowerCase()) : true, ) .map(async (testCase) => ({ @@ -266,7 +266,7 @@ export const runDebugtopus = async (options: DebugtopusOptions) => { export const executeLocalTestCases = async ( options: DebugtopusOptions, ): Promise => { - const octomindRoot = await findOctomindFolder() + const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { throw new Error( `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to execute locally`, diff --git a/src/helpers.ts b/src/helpers.ts index 59fd43f..06fd58e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,12 +1,11 @@ +import path from "node:path"; import { stdin as input, stdout as output } from "node:process"; import { createInterface } from "node:readline"; +import fsPromises from "fs/promises"; import { loadConfig } from "./config"; -import path from "node:path"; -import fsPromises from "fs/promises"; import { OCTOMIND_FOLDER_NAME } from "./constants"; - export function promptUser(question: string): Promise { const rl = createInterface({ input, output }); @@ -34,7 +33,6 @@ export const resolveTestTargetId = async ( return config.testTargetId; }; - const isDirectory = async (dirPath: string): Promise => { try { const stat = await fsPromises.stat(dirPath); @@ -60,16 +58,22 @@ export const findOctomindFolder = async (): Promise => { return rootOctomind; } - return null + return null; }; -export const getAbsoluteFilePathInOctomindRoot = async ({ filePath, octomindRoot }: { filePath: string, octomindRoot: string }): Promise => { +export const getAbsoluteFilePathInOctomindRoot = async ({ + filePath, + octomindRoot, +}: { + filePath: string; + octomindRoot: string; +}): Promise => { try { const resolvedPath = await fsPromises.realpath( - path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath) + path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath), ); return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; } catch { return null; } -} +}; diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts index ab41e77..c831fe0 100644 --- a/src/tools/test-cases.ts +++ b/src/tools/test-cases.ts @@ -1,11 +1,12 @@ +import fsPromises from "fs/promises"; import path from "path"; + import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; import { buildFilename, readTestCasesFromDir } from "./sync/yml"; -import fsPromises from "fs/promises"; export type TestCaseResponse = components["schemas"]["TestCaseResponse"]; export type TestCasesResponse = components["schemas"]["TestCasesResponse"]; @@ -17,7 +18,7 @@ export type DeleteTestCaseParams = export const deleteTestCase = async ( options: DeleteTestCaseParams & ListOptions, ): Promise => { - const octomindRoot = await findOctomindFolder() + const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { const { data, error } = await client.DELETE( @@ -37,23 +38,22 @@ export const deleteTestCase = async ( logJson(data); } console.log("Test Case deleted successfully"); - return + return; } const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); - const existingTestCase = testCasesById[options.testCaseId] + const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log(`No test case with id ${options.testCaseId} found in folder ${octomindRoot}`) - return + console.log( + `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, + ); + return; } - const existingTestCasePath = buildFilename( - existingTestCase, - octomindRoot, - ); - await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)) + const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); + await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); console.log("Test Case deleted successfully"); }; diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index 0b2238f..77b185f 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -1,11 +1,11 @@ import path from "path"; +import { OCTOMIND_FOLDER_NAME } from "../constants"; +import { findOctomindFolder } from "../helpers"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; import { writeYaml } from "./sync/yml"; -import { findOctomindFolder } from "../helpers"; -import { OCTOMIND_FOLDER_NAME } from "../constants"; export const getTestTargets = async () => { const { data, error } = await client.GET("/apiKey/v3/test-targets"); @@ -70,7 +70,9 @@ export const pullTestTarget = async ( return; } - const destination = await findOctomindFolder() ?? path.join(process.cwd(), OCTOMIND_FOLDER_NAME) + const destination = + (await findOctomindFolder()) ?? + path.join(process.cwd(), OCTOMIND_FOLDER_NAME); writeYaml(data, destination); console.log("Test Target pulled successfully"); @@ -79,9 +81,11 @@ export const pullTestTarget = async ( export const pushTestTarget = async ( options: { testTargetId: string } & ListOptions, ): Promise => { - const sourceDir = await findOctomindFolder() + const sourceDir = await findOctomindFolder(); if (!sourceDir) { - throw new Error(`No ${OCTOMIND_FOLDER_NAME} folder found, please pull first.`) + throw new Error( + `No ${OCTOMIND_FOLDER_NAME} folder found, please pull first.`, + ); } const data = await push({ diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 09ee3e5..689c309 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -3,20 +3,22 @@ import path from "path"; import yaml from "yaml"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; +import { + findOctomindFolder, + getAbsoluteFilePathInOctomindRoot, +} from "../../helpers"; import { client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir } from "../sync/yml"; -import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; -import { OCTOMIND_FOLDER_NAME } from "../../constants"; type EditOptions = { testTargetId: string; filePath: string; }; - const getRelevantTestCases = ( testCasesById: Record, startTestCase: SyncTestCase, @@ -56,9 +58,14 @@ export const edit = async (options: EditOptions): Promise => { `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } - const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ + octomindRoot, + filePath: options.filePath, + }); if (!testCaseFilePath) { - throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) + throw new Error( + `Could not find ${options.filePath} in folder ${octomindRoot}`, + ); } const testCaseToEdit = loadTestCase(testCaseFilePath); diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index 2b28b27..a4f65b6 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -2,11 +2,11 @@ import fsPromises from "fs/promises"; import os from "os"; import path from "path"; +import { OCTOMIND_FOLDER_NAME } from "../src/constants"; import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../src/helpers"; -import { OCTOMIND_FOLDER_NAME } from "../src/constants"; describe("helpers", () => { describe("findOctomindFolder", () => { @@ -14,7 +14,9 @@ describe("helpers", () => { const originalCwd = process.cwd; beforeEach(async () => { - tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "octomind-test-")); + tmpDir = await fsPromises.mkdtemp( + path.join(os.tmpdir(), "octomind-test-"), + ); }); afterEach(async () => { @@ -91,7 +93,9 @@ describe("helpers", () => { let octomindRoot: string; beforeEach(async () => { - tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "octomind-test-")); + tmpDir = await fsPromises.mkdtemp( + path.join(os.tmpdir(), "octomind-test-"), + ); octomindRoot = path.join(tmpDir, OCTOMIND_FOLDER_NAME); await fsPromises.mkdir(octomindRoot); }); @@ -173,7 +177,12 @@ describe("helpers", () => { it("should return null for malformed path with nested octomind structure", async () => { // e.g. /some/folder/.octomind/some/folder/.octomind/a.yaml - const malformedPath = path.join(octomindRoot, tmpDir, OCTOMIND_FOLDER_NAME, "a.yaml"); + const malformedPath = path.join( + octomindRoot, + tmpDir, + OCTOMIND_FOLDER_NAME, + "a.yaml", + ); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: malformedPath, diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 5ccac83..49e1d9b 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -2,9 +2,9 @@ import fsPromises from "fs/promises"; import os from "os"; import path from "path"; -import { client, handleError } from "../../src/tools/client"; import { findOctomindFolder } from "../../src/helpers"; -import { readTestCasesFromDir, buildFilename } from "../../src/tools/sync/yml"; +import { client, handleError } from "../../src/tools/client"; +import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yml"; import { deleteTestCase } from "../../src/tools/test-cases"; jest.mock("../../src/tools/client"); @@ -40,7 +40,9 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith("Test Case deleted successfully"); + expect(console.log).toHaveBeenCalledWith( + "Test Case deleted successfully", + ); }); it("should handle error", async () => { @@ -76,9 +78,11 @@ describe("test-cases", () => { const filePath = path.join(tmpDir, fileName); await fsPromises.writeFile(filePath, ""); - jest.mocked(readTestCasesFromDir).mockReturnValue([ - { id: testCaseId, description: "My Test Case" }, - ] as ReturnType); + jest + .mocked(readTestCasesFromDir) + .mockReturnValue([ + { id: testCaseId, description: "My Test Case" }, + ] as ReturnType); jest.mocked(buildFilename).mockReturnValue(fileName); await deleteTestCase({ @@ -86,7 +90,9 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith("Test Case deleted successfully"); + expect(console.log).toHaveBeenCalledWith( + "Test Case deleted successfully", + ); await expect(fsPromises.access(filePath)).rejects.toThrow(); }); From 64b2b87153c4c5cb2b7627b1f520e4eb17b69679 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Tue, 13 Jan 2026 17:58:09 +0100 Subject: [PATCH 13/36] fix test case path --- src/helpers.ts | 13 ++++++------- tests/helpers.spec.ts | 42 ++++-------------------------------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 06fd58e..c6cce4d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -68,12 +68,11 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ filePath: string; octomindRoot: string; }): Promise => { - try { - const resolvedPath = await fsPromises.realpath( - path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath), - ); - return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; - } catch { - return null; + let resolvedPath: string + if (path.isAbsolute(filePath)) { + resolvedPath = filePath + } else { + resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)) } + return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; }; diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index a4f65b6..aed9450 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -105,34 +105,29 @@ describe("helpers", () => { }); it("should resolve relative path within octomind root", async () => { - const filePath = path.join(octomindRoot, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); + const expectedPath = path.join(octomindRoot, "test-case.yaml"); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "test-case.yaml", octomindRoot, }); - expect(result).toBe(filePath); + expect(result).toBe(expectedPath); }); it("should resolve nested relative path within octomind root", async () => { - const subdir = path.join(octomindRoot, "subdir"); - await fsPromises.mkdir(subdir); - const filePath = path.join(subdir, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); + const expectedPath = path.join(octomindRoot, "subdir", "test-case.yaml"); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "subdir/test-case.yaml", octomindRoot, }); - expect(result).toBe(filePath); + expect(result).toBe(expectedPath); }); it("should accept absolute path within octomind root", async () => { const filePath = path.join(octomindRoot, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath, @@ -144,7 +139,6 @@ describe("helpers", () => { it("should return null for absolute path outside octomind root", async () => { const outsideFile = path.join(tmpDir, "outside.yaml"); - await fsPromises.writeFile(outsideFile, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: outsideFile, @@ -155,9 +149,6 @@ describe("helpers", () => { }); it("should return null for path traversal attempts", async () => { - const outsideFile = path.join(tmpDir, "outside.yaml"); - await fsPromises.writeFile(outsideFile, ""); - const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "../outside.yaml", octomindRoot, @@ -166,30 +157,5 @@ describe("helpers", () => { expect(result).toBeNull(); }); - it("should return null for non-existent file", async () => { - const result = await getAbsoluteFilePathInOctomindRoot({ - filePath: "does-not-exist.yaml", - octomindRoot, - }); - - expect(result).toBeNull(); - }); - - it("should return null for malformed path with nested octomind structure", async () => { - // e.g. /some/folder/.octomind/some/folder/.octomind/a.yaml - const malformedPath = path.join( - octomindRoot, - tmpDir, - OCTOMIND_FOLDER_NAME, - "a.yaml", - ); - - const result = await getAbsoluteFilePathInOctomindRoot({ - filePath: malformedPath, - octomindRoot, - }); - - expect(result).toBeNull(); - }); }); }); From 0fa502abd35a9354c678e3bcbd894c74406d97e0 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 13 Jan 2026 18:09:11 +0100 Subject: [PATCH 14/36] add open dependency, finish editing implementation --- README.md | 14 +++++ package.json | 1 + pnpm-lock.yaml | 96 +++++++++++++++++++++++++++++++++ src/helpers.ts | 3 ++ src/tools/sync/push.ts | 9 +++- src/tools/sync/yml.ts | 20 +++++-- src/tools/test-reports.ts | 4 +- src/tools/test-targets.ts | 2 +- src/tools/yamlMutations/edit.ts | 76 ++++++++++++++++++++++++-- 9 files changed, 213 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 90441bb..0bad194 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,20 @@ Push local YAML test cases to the test target | `-j, --json` | Output raw JSON response | No | | | `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | | +## edit-test-case + +Edit yaml test case + +**Usage:** `edit-test-case [options]` + +### Options + +| Option | Description | Required | Default | +|:-------|:----------|:---------|:--------| +| `-j, --json` | Output raw JSON response | No | | +| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | | +| `-f, --file-path ` | The path to the local yaml file you want to edit | Yes | | + ## Test Reports ## test-report diff --git a/package.json b/package.json index 0c31ce4..548f3f3 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", + "open": "11.0.0", "openapi-fetch": "0.15.0", "otplib": "13.0.2", "shell-quote": "1.8.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fd41ac..233de0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: commander: specifier: 14.0.2 version: 14.0.2 + open: + specifier: 11.0.0 + version: 11.0.0 openapi-fetch: specifier: 0.15.0 version: 0.15.0 @@ -1371,6 +1374,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1533,10 +1540,22 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1961,6 +1980,11 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1989,6 +2013,15 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2045,6 +2078,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -2512,6 +2549,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + openapi-fetch@0.15.0: resolution: {integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==} @@ -2645,6 +2686,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + pretty-format@30.0.5: resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2714,6 +2759,10 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -3191,6 +3240,10 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -4687,6 +4740,10 @@ snapshots: buffer-from@1.1.2: {} + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4822,12 +4879,21 @@ snapshots: deepmerge@4.3.1: {} + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -5391,6 +5457,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -5415,6 +5483,12 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -5467,6 +5541,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -6123,6 +6201,15 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@11.0.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + openapi-fetch@0.15.0: dependencies: openapi-typescript-helpers: 0.0.15 @@ -6276,6 +6363,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + powershell-utils@0.1.0: {} + pretty-format@30.0.5: dependencies: '@jest/schemas': 30.0.5 @@ -6351,6 +6440,8 @@ snapshots: reusify@1.1.0: {} + run-applescript@7.1.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -6918,6 +7009,11 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.0 + powershell-utils: 0.1.0 + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/src/helpers.ts b/src/helpers.ts index c6cce4d..f1e9a54 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -76,3 +76,6 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ } return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; }; + +export const sleep = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/tools/sync/push.ts b/src/tools/sync/push.ts index 173f548..fe8d689 100644 --- a/src/tools/sync/push.ts +++ b/src/tools/sync/push.ts @@ -68,7 +68,14 @@ const defaultPush = async ( export const draftPush = async ( body: TestTargetSyncData, options: Omit & ListOptions, -): Promise<{ success: boolean; versionIds: string[] } | undefined> => { +): Promise< + | { + success: boolean; + versionIds: string[]; + versionIdByStableId: Record; + } + | undefined +> => { const { data, error } = await options.client.POST( "/apiKey/beta/test-targets/{testTargetId}/draft/push", { diff --git a/src/tools/sync/yml.ts b/src/tools/sync/yml.ts index f0aa73e..e0d28db 100644 --- a/src/tools/sync/yml.ts +++ b/src/tools/sync/yml.ts @@ -1,4 +1,5 @@ import fs from "fs"; +import fsPromises from "fs/promises"; import path from "path"; import yaml from "yaml"; @@ -40,7 +41,20 @@ const toFileSystemCompatibleCamelCase = (description: string): string => { return camelCased; }; -export const writeYaml = (data: TestTargetSyncData, destination?: string) => { +export const writeSingleTestCaseYaml = async ( + filePath: string, + testCase: SyncTestCase, +): Promise => { + return fsPromises.writeFile( + filePath, + `# yaml-language-server: $schema=https://app.octomind.dev/schemas/SyncTestCaseSchema.json\n${yaml.stringify(testCase)}`, + ); +}; + +export const writeYaml = async ( + data: TestTargetSyncData, + destination?: string, +): Promise => { cleanupFilesystem({ newTestCases: data.testCases, destination, @@ -50,9 +64,9 @@ export const writeYaml = (data: TestTargetSyncData, destination?: string) => { const folderName = buildFolderName(testCase, data.testCases, destination); const testCaseFilename = buildFilename(testCase, folderName); fs.mkdirSync(folderName, { recursive: true }); - fs.writeFileSync( + await writeSingleTestCaseYaml( path.join(folderName, testCaseFilename), - `# yaml-language-server: $schema=https://app.octomind.dev/schemas/SyncTestCaseSchema.json\n${yaml.stringify(testCase)}`, + testCase, ); } }; diff --git a/src/tools/test-reports.ts b/src/tools/test-reports.ts index 9f543e5..1e5e509 100644 --- a/src/tools/test-reports.ts +++ b/src/tools/test-reports.ts @@ -46,7 +46,7 @@ export const executeTests = async ( if (numberOfTestResults > 0) { console.log("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseId}: ${result.status}`); + console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { console.log(` Error: ${result.errorMessage}`); } @@ -107,7 +107,7 @@ export const listTestReport = async ( if (numberOfTestResults > 0) { console.log("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseId}: ${result.status}`); + console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); console.log( ` ${await getUrl({ testReportId: options.testReportId, diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index 77b185f..ada58cb 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -73,7 +73,7 @@ export const pullTestTarget = async ( const destination = (await findOctomindFolder()) ?? path.join(process.cwd(), OCTOMIND_FOLDER_NAME); - writeYaml(data, destination); + await writeYaml(data, destination); console.log("Test Target pulled successfully"); }; diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 689c309..7963cbf 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,18 +1,20 @@ -import fs from "fs"; +import fs, { writeFileSync } from "fs"; import path from "path"; +import open from "open"; import yaml from "yaml"; import { OCTOMIND_FOLDER_NAME } from "../../constants"; import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, + sleep, } from "../../helpers"; -import { client, handleError } from "../client"; +import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; -import { readTestCasesFromDir } from "../sync/yml"; +import { readTestCasesFromDir, writeSingleTestCaseYaml } from "../sync/yml"; type EditOptions = { testTargetId: string; @@ -51,6 +53,25 @@ const loadTestCase = (testCasePath: string): SyncTestCase => { } }; +const POLLING_INTERVAL = 1000; +const getTestCaseToEdit = async ( + versionId: string, + testCaseToEdit: SyncTestCase, + options: EditOptions, +) => { + return await client.GET( + "/apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}", + { + params: { + path: { + versionId, + testCaseId: testCaseToEdit.id, + testTargetId: options.testTargetId, + }, + }, + }, + ); +}; export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -58,17 +79,24 @@ export const edit = async (options: EditOptions): Promise => { `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } + + console.log({ octomindRoot }); const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath, }); + console.log({ testCaseFilePath }); + if (!testCaseFilePath) { throw new Error( `Could not find ${options.filePath} in folder ${octomindRoot}`, ); } - const testCaseToEdit = loadTestCase(testCaseFilePath); + const testCaseToEdit = { + ...loadTestCase(testCaseFilePath), + localEditingStatus: "IN_PROGRESS", + }; const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); @@ -87,5 +115,43 @@ export const edit = async (options: EditOptions): Promise => { }, ); - console.log(response); + if (!response) { + throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); + } + + const versionId = response?.versionIdByStableId[testCaseToEdit.id]; + + if (!versionId) { + throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); + } + + const parsedBaseUrl = URL.parse(BASE_URL); + const localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; + await open(localEditingUrl); + + console.log( + `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, + ); + + let localTestCase = await getTestCaseToEdit( + versionId, + testCaseToEdit, + options, + ); + + while (localTestCase.data?.localEditingStatus === "IN_PROGRESS") { + await sleep(POLLING_INTERVAL); + console.log("Waiting for local editing to finish..."); + + localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); + } + + await writeSingleTestCaseYaml(testCaseFilePath, { + ...localTestCase.data, + id: testCaseToEdit.id, + localEditingStatus: undefined, + versionId: undefined, + }); + + console.log("Edited test case successfully!"); }; From f6e85cecd71435c8fb781c0d315f9d275f897416 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Tue, 13 Jan 2026 18:17:20 +0100 Subject: [PATCH 15/36] absolute files in octomind root --- src/helpers.ts | 31 ++++++++++++++++++++++++++----- tests/helpers.spec.ts | 22 +++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index c6cce4d..a3e11d3 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -68,11 +68,32 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ filePath: string; octomindRoot: string; }): Promise => { - let resolvedPath: string + const isWithinOctomindRoot = (p: string) => p.startsWith(octomindRoot); + + const isFile = async (p: string): Promise => { + try { + const stats = await fsPromises.stat(p); + return stats.isFile(); + } catch { + return false; + } + }; + if (path.isAbsolute(filePath)) { - resolvedPath = filePath - } else { - resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)) + return isWithinOctomindRoot(filePath) ? filePath : null; } - return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; + + // For relative paths, try resolving from cwd first, then from octomindRoot + const candidates = [ + path.resolve(filePath), + path.resolve(octomindRoot, filePath), + ]; + + for (const candidate of candidates) { + if (isWithinOctomindRoot(candidate) && (await isFile(candidate))) { + return candidate; + } + } + + return null; }; diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index aed9450..59641cc 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -105,25 +105,38 @@ describe("helpers", () => { }); it("should resolve relative path within octomind root", async () => { - const expectedPath = path.join(octomindRoot, "test-case.yaml"); + const filePath = path.join(octomindRoot, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "test-case.yaml", octomindRoot, }); - expect(result).toBe(expectedPath); + expect(result).toBe(filePath); }); it("should resolve nested relative path within octomind root", async () => { - const expectedPath = path.join(octomindRoot, "subdir", "test-case.yaml"); + const subdir = path.join(octomindRoot, "subdir"); + await fsPromises.mkdir(subdir); + const filePath = path.join(subdir, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "subdir/test-case.yaml", octomindRoot, }); - expect(result).toBe(expectedPath); + expect(result).toBe(filePath); + }); + + it("should return null for non-existent relative path", async () => { + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "does-not-exist.yaml", + octomindRoot, + }); + + expect(result).toBeNull(); }); it("should accept absolute path within octomind root", async () => { @@ -156,6 +169,5 @@ describe("helpers", () => { expect(result).toBeNull(); }); - }); }); From 2388cfc05a62bc959efbf998a3aacbb5980a0f77 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 13 Jan 2026 19:20:14 +0100 Subject: [PATCH 16/36] working version --- openapi.yaml | 4735 +++++++++++++++++++++++++++++++ package.json | 3 +- pnpm-lock.yaml | 9 + src/tools/yamlMutations/edit.ts | 49 +- 4 files changed, 4782 insertions(+), 14 deletions(-) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..e3774b0 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,4735 @@ +openapi: 3.1.0 +info: + title: Octomind external API + description: Octomind API that allows you to execute test cases, fetch reports and register private location workers by providing a URL and an ID. + version: 1.0.0 + contact: + name: API Support + url: https://octomind.dev/docs + email: support@octomind.dev +servers: + - url: https://app.octomind.dev/api + description: Main API Endpoint +tags: + - name: Test targets + description: Operations for managing test targets + - name: Execute + description: Operations for executing tests + - name: Environments + description: Operations for managing test environments + - name: Reports + description: Operations for retrieving test reports + - name: Notifications + description: Operations for retrieving notifications + - name: Private locations + description: Operations for managing private locations + - name: Test Cases + description: Operations for retrieving test cases + - name: Discoveries + description: Operations for creating test discoveries +externalDocs: + description: Find out more + url: https://octomind.dev/docs/ +paths: + /apiKey/v3/test-targets: + get: + summary: Retrieve all test targets + description: Gets a list of test targets. + operationId: getTestTargets + tags: + - Test targets + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetsResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + post: + summary: Create a new test target + description: Creates a new test target. + operationId: createTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTestTargetBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}: + get: + summary: Retrieve a test target + description: Gets a test target by ID. + operationId: getTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to fetch + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + patch: + summary: Update a test target + description: Updates a test target by ID. + operationId: updateTestTarget + tags: + - Test targets + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to update + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetUpdateRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + delete: + summary: Delete a test target + description: Deletes a test target by ID. + operationId: deleteTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to delete + responses: + '204': + description: OK + '401': + description: Invalid or missing API key + '404': + description: Test target not found + '500': + description: Internal server error + /apiKey/v3/execute: + post: + summary: Execute tests of the given test target + description: | + This endpoint triggers a test execution by sending an test target id, an URL and optionally tags, an environment and variables. + operationId: executeTests + tags: + - Execute + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetExecutionRequest' + responses: + '200': + description: Test executed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestExecutionResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/batch-generations: + post: + summary: Create a batch generation + description: Creates a batch generation for the given test target. + operationId: createBatchGeneration + tags: + - Discoveries + security: + - ApiKeyAuth: [] + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalBatchGenerationBody' + responses: + '200': + description: Batch generation created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGenerationResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/config: + get: + summary: Retrieve test target configuration + description: Get the test target configuration for a specific environment + operationId: getTestTargetConfig + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: url + in: query + required: true + schema: + type: string + format: uri + description: The execution URL for the test target + - name: outputDir + in: query + required: true + schema: + type: string + description: The directory where test output will be stored + - name: headless + in: query + required: false + schema: + type: string + description: Whether to run tests in headless mode (true/false) + - name: environmentId + in: query + required: false + schema: + type: string + format: uuid + description: Optional ID of the environment to use (defaults to the test target's default environment if not provided) + responses: + '200': + description: Test target configuration retrieved successfully + content: + text/plain: + schema: + type: string + description: The test target configuration as plain text + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Environment not found + '405': + description: Method not allowed + /apiKey/v3/test-targets/{testTargetId}/environments: + post: + summary: Create an environment + description: Create a custom environment. + operationId: createEnvironment + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the environment belongs to + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + discoveryUrl: + type: string + format: url + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + nullable: true + privateLocationName: + type: string + description: name of the private location + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + responses: + '201': + description: environment created + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + get: + summary: Retrieve environments + description: get a list of all defined environments. + operationId: getEnvironments + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + responses: + '200': + description: environments + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentsResponse' + /apiKey/v3/test-targets/{testTargetId}/environments/{environmentId}: + patch: + summary: Update an environment + description: Updates an enviroment, all properties can be set separately + operationId: updateEnvironment + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the environment belongs to + - in: path + name: environmentId + required: true + schema: + type: string + format: uuid + description: ID of the environment to update + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + nullable: true + discoveryUrl: + type: string + format: url + nullable: true + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + nullable: true + privateLocationName: + type: string + nullable: true + description: name of the private location + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + responses: + '200': + description: Environment updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentResponse' + delete: + summary: Delete an environment + operationId: deleteEnvironment + description: deletes an enviroment. this operation is not reversable. + security: + - ApiKeyAuth: [] + tags: + - Environments + responses: + '200': + description: Environment deleted successfully + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + - in: path + name: environmentId + required: true + schema: + type: string + format: uuid + description: ID of the environment to update + /apiKey/v3/test-targets/{testTargetId}/test-reports/{testReportId}: + get: + summary: Retrieve information about a test report + description: Poll from within a CI-pipeline to wait for the completion of a report. + operationId: getTestReport + tags: + - Reports + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + - in: path + name: testReportId + required: true + schema: + type: string + format: uuid + description: ID of the test report to fetch + security: + - ApiKeyAuth: [] + responses: + '200': + description: Test Report information + content: + application/json: + schema: + $ref: '#/components/schemas/TestReport' + example: + id: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:05:23.412Z' + executionUrl: https://en.wikipedia.org/ + status: FAILED + context: + source: github + issueNumber: 123 + ref: refs/heads/main + sha: abc123def456 + repo: my-repo + owner: repo-owner + triggeredBy: + type: USER + userId: 3435918b-3d29-4ebd-8c68-9a540532f45a + nodeId: node-123 + testResults: + - id: 9876fedc-ba98-7654-3210-fedcba987654 + testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:03:12.345Z' + status: PASSED + errorMessage: null + traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip + - id: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: 5432fedc-ba98-7654-3210-fedcba543210 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:04:45.678Z' + status: FAILED + errorMessage: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' + traceUrl: https://storage.googleapis.com/automagically-traces/2a3b4c5d-6e7f-8g9h-0i1j-2k3l4m5n6o7p-trace.zip + - id: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: 1234abcd-ef56-7890-abcd-ef1234567890 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:05:23.412Z' + status: WAITING + errorMessage: null + traceUrl: null + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-reports: + get: + summary: Retrieve paginated information about test reports + description: Allow fetching the history of test reports for your test target. + operationId: getTestReports + tags: + - Reports + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target for which to fetch the history for + - in: query + name: key + required: false + schema: + type: object + properties: + createdAt: + type: string + format: date-time + description: The timestamp of the key of the next page to fetch - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) + example: '2024-09-06T13:01:51.686Z' + - in: query + name: filter + required: false + schema: + type: array + items: + type: object + properties: + key: + type: string + description: The name of the property to filter for, e.g. an environmentId + example: environmentId + operator: + type: string + enum: + - EQUALS + description: How to compare the property in question, only EQUALS is supported so far. + value: + type: string + format: uuid + description: The value to compare with to find matches. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + security: + - ApiKeyAuth: [] + responses: + '200': + description: Test Reports information + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/TestReport' + key: + type: object + properties: + createdAt: + type: string + format: date-time + description: The timestamp of the key of the next page to fetch - use this in the query when fetching the next page of reports - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) + example: '2024-09-06T13:01:51.686Z' + hasNextPage: + type: boolean + description: If the query in question has another page to retrieve + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/notifications: + get: + summary: Retrieve notifications + description: Get a list of notifications for a specific test target. + operationId: getNotifications + security: + - ApiKeyAuth: [] + tags: + - Notifications + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + responses: + '200': + description: List of notifications + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Notification' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/v1/private-location: + get: + security: + - ApiKeyAuth: [] + summary: Retrieve all private locations + description: gets a list of private location workers + operationId: getPrivateLocations + tags: + - Private locations + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PrivateLocationInfo' + /apiKey/v1/private-location/register: + put: + security: + - ApiKeyAuth: [] + summary: Register a private location + description: registers a private location worker + operationId: registerPrivateLocation + tags: + - Private locations + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '404': + description: private location of that name not found + '500': + description: Internal server error + /apiKey/v1/private-location/unregister: + put: + security: + - ApiKeyAuth: [] + summary: Unregister a private location + description: Unregisters a private location worker. + operationId: unregisterPrivateLocation + tags: + - Private locations + externalDocs: + url: https://octomind.dev/docs/proxy/private-location + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnregisterRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '404': + description: private location of that name not found + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-cases: + get: + summary: List test cases + description: Get a list of test cases for a specific test target with optional filtering + operationId: getTestCases + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: filter + in: query + required: false + schema: + type: string + description: | + JSON string containing filter criteria for test cases. The filter supports the following fields: + - testTargetId: Filter by test target ID + - description: Filter by test case description + - status: Filter by test case status (ENABLED, DISABLED, DRAFT, OUTDATED, DELETED, PROVISIONAL) + - runStatus: Filter by run status (ON, OFF) + - folderId: Filter by folder ID + - externalId: Filter by external ID + - AND: Logical AND operator for combining multiple conditions + - OR: Logical OR operator for combining multiple conditions + - NOT: Logical NOT operator for negating conditions + example: '{"status":"ENABLED","folderId":"some-folder-id","OR":[{"description":"Login Test"},{"externalId":"TEST-123"}]}' + responses: + '200': + description: List of test cases + content: + application/json: + schema: + $ref: '#/components/schemas/TestCasesResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}: + get: + summary: Retrieve a test case + description: Get detailed information about a specific test case + operationId: getTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + responses: + '200': + description: Test case details + content: + application/json: + schema: + $ref: '#/components/schemas/TestCaseResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target or test case not found + patch: + summary: Update a test case + description: Update specific properties of a test case + operationId: patchTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case to update + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + elements: + type: array + items: + $ref: '#/components/schemas/TestCaseElement' + description: + type: string + entryPointUrlPath: + type: string + nullable: true + status: + type: string + enum: + - ENABLED + - DISABLED + - DRAFT + - OUTDATED + - PROVISIONAL + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + folderName: + type: string + nullable: true + interactionStatus: + type: string + enum: + - NEW + - EDITED + - APPROVED + - REJECTED + createBackendDiscoveryPrompt: + type: string + assignedTagNames: + type: array + items: + type: string + externalId: + type: string + nullable: true + responses: + '200': + description: Updated test case + content: + application/json: + schema: + type: object + properties: + id: + type: string + format: uuid + testTargetId: + type: string + format: uuid + description: + type: string + status: + type: string + enum: + - ENABLED + - DISABLED + - DRAFT + - OUTDATED + - PROVISIONAL + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + entryPointUrlPath: + type: string + nullable: true + elements: + type: array + items: + type: object + folderId: + type: string + nullable: true + externalId: + type: string + nullable: true + tags: + type: array + items: + type: string + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case, folder or tag not found + delete: + summary: Delete a test case + description: Delete a specific test case + operationId: deleteTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + responses: + '200': + description: Test case deleted successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: + - deleted + description: Status of the deletion operation + required: + - status + '400': + description: Bad request + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ZodResponse' + - type: object + properties: + status: + type: string + enum: + - has dependencies + description: Indicates the test case has dependencies + dependencyIds: + type: array + items: + type: string + format: uuid + description: List of test case IDs that depend on this test case + required: + - status + - dependencyIds + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case not found + '500': + description: Internal server error + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: + - error + description: Indicates an error occurred + error: + type: string + description: Error message + required: + - status + - error + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/elements/{elementId}: + patch: + summary: Update a test case element + description: Update a the locator line of a specific test case element + operationId: updateTestCaseElement + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: elementId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case element + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + locatorLine: + description: The locator line of the test case element + type: string + required: + - locatorLine + responses: + '200': + description: Test case element updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestCaseElement' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target, test case or test case element not found + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/code: + get: + summary: Retrieve code for a test case + description: Get the code representation of a specific test case + operationId: getTestCaseCode + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: executionUrl + in: query + required: true + schema: + type: string + format: url + description: URL of the app to test + - name: environmentId + in: query + required: false + schema: + type: string + format: uuid + description: Optional ID of the environment to use + responses: + '200': + description: Test case code retrieved successfully + content: + application/json: + schema: + type: object + properties: + testCode: + type: string + description: The code representation of the test case + required: + - testCode + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target, test case, or test code not found + /apiKey/beta/test-targets/{testTargetId}/pull: + get: + summary: Pull all test case from a test target to use it locally as json or yaml and work with the files directly. There is also a "opposite" endpoint to push the files back to the test target. + description: Get the pull data for a specific test target + operationId: getTestTargetPullData + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + responses: + '200': + description: Test target pull data retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/code: + post: + summary: Get TestSuite code + description: Retrieve the typescript code in a zip file for a set of yaml based test cases. This code can then be executed by playwright. + operationId: getTestTargetCode + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetCodeSchema' + responses: + '200': + description: Test target code retrieved successfully + content: + application/zip: + schema: + type: string + format: binary + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/push: + post: + summary: Push test target + description: Pushes all test cases with the specified schema to the test target. There is also a "opposite" endpoint to pull the files back to the local machine. + operationId: pushTestTarget + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + responses: + '200': + description: Test target pushed successfully + content: + application/json: + schema: + type: object + required: + - success + - versionIds + properties: + success: + type: boolean + versionIds: + type: array + items: + type: string + format: uuid + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/draft/push: + post: + summary: Push draft test target + description: Push all test cases from a local representation to a test target as a draft. + operationId: pushTestTargetDraft + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + responses: + '200': + description: Test target pushed successfully + content: + application/json: + schema: + type: object + required: + - success + - versionIds + - versionIdByStableId + properties: + success: + type: boolean + versionIds: + type: array + items: + type: string + format: uuid + description: List of test case IDs that were pushed to the test target + versionIdByStableId: + type: object + additionalProperties: + type: string + format: uuid + example: + 5afe4190-5900-47d0-8cfd-bbb7dce2460e: 48c9e7d0-67e3-4806-b255-3b9f79fa9639 + description: Version IDs per stable id that were pushed to the test target + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}: + get: + summary: Retrieve a specific test case version + description: Get detailed information about a specific version of a test case, including its local editing status. + operationId: getTestCaseVersion + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: versionId + in: path + required: true + schema: + type: string + format: uuid + description: The version ID of the test case + responses: + '200': + description: Test case version details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/items' + - type: object + required: + - versionId + - localEditingStatus + properties: + versionId: + type: string + format: uuid + description: The version ID of this test case + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + nullable: true + description: The local editing status of this test case version + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case not found + /apiKey/v3/test-targets/{testTargetId}/discoveries: + post: + summary: Create a discovery + description: Create a new test case discovery with a given name and prompt + operationId: createDiscovery + security: + - ApiKeyAuth: [] + tags: + - Discoveries + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalDiscoveryBody' + responses: + '200': + description: Discovery created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/DiscoveryResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + '500': + description: Internal server error +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + schemas: + Breakpoint: + type: string + enum: + - MOBILE + - TABLET + - DESKTOP + description: The breakpoint to run the test cases against. + example: DESKTOP + default: DESKTOP + Browser: + type: string + enum: + - CHROMIUM + - FIREFOX + - SAFARI + description: The browser to run the test cases against. + example: CHROMIUM + default: CHROMIUM + TestTargetExecutionRequest: + type: object + properties: + testTargetId: + type: string + format: uuid + description: Unique identifier for the testTarget. + example: 2e2bb27b-a19c-47ce-a9b6-cd1bd31622dc + url: + type: string + format: uri + description: The URL of the test target for this run. + example: https://example.com + context: + $ref: '#/components/schemas/ExecutionContext' + environmentName: + type: string + format: environment name + description: the environment name you want to run your test against + default: default + variablesToOverwrite: + $ref: '#/components/schemas/Variables' + tags: + type: array + items: + type: string + example: + - tag1 + - tag2 + default: [] + description: The tags to filter the test cases by. + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + testCaseVersionIds: + type: array + items: + type: string + format: uuid + required: + - testTargetId + - url + - context + TestResult: + type: object + example: + id: 333d3f57-00cf-4ce9-8d34-89093a08681a + testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:03:12.345Z' + status: PASSED + errorMessage: null + traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip + properties: + id: + type: string + format: uuid + description: Unique identifier for the test result. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: Unique identifier of the test report this result belongs to. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: + type: string + format: uuid + description: Unique identifier of the test case version this result belongs to. + example: 5b844cf1-d597-4048-9e74-7c0f9ce3e2ee + createdAt: + type: string + format: date-time + description: The timestamp when the test result was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the test result was last updated. + example: '2024-09-06T13:01:51.686Z' + status: + type: string + enum: + - WAITING + - PASSED + - FAILED + - ERROR + description: The status of the specific test result, will be WAITING as long as the result is running, FAILED if the execution failed, PASSED if it succeeded, and ERROR if an internal error occurred. + errorMessage: + type: string + nullable: true + description: The error that has occurred during execution - only set if the status is FAILED or ERROR. + example: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' + traceUrl: + type: string + nullable: true + description: Link to the playwright trace of the test execution - only set once the test result is finished (PASSED or FAILED). + example: https://storage.googleapis.com/automagically-traces/826c15af-644b-4b28-89b4-f50ff34e46b7-trace.zip + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + TestReport: + type: object + required: + - id + - testTargetId + - createdAt + - updatedAt + - executionUrl + - status + - context + properties: + id: + type: string + format: uuid + description: Unique identifier for the test report. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: The unique identifier of the test target. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: + type: string + format: date-time + description: The timestamp when the test report was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the test report was last updated. + example: '2024-09-06T13:01:51.686Z' + executionUrl: + type: string + format: uri + description: The URL where the test execution was performed. + example: https://en.wikipedia.org/ + status: + type: string + enum: + - WAITING + - PASSED + - FAILED + description: The status of the test report, will be WAITING as long as any result is running, FAILED if the report is done but has any failed result and PASSED if all test results are done and successful + context: + $ref: '#/components/schemas/ExecutionContext' + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + testResults: + type: array + items: + $ref: '#/components/schemas/TestResult' + PrivateLocationInfo: + type: array + items: + type: object + properties: + status: + type: string + enum: + - OFFLINE + - ONLINE + address: + type: string + format: uri + example: https://example.com:3128 + name: + type: string + example: my-private-location + required: + - status + - address + - name + TestExecutionResponse: + type: object + required: + - testReportUrl + - testReport + properties: + testReportUrl: + type: string + format: uri + description: The URL where the test report can be accessed. + example: https://app.octomind.dev/testreports/826c15af-644b-4b28-89b4-f50ff34e46b7 + testReport: + $ref: '#/components/schemas/TestReport' + TestCasesResponse: + type: array + items: + $ref: '#/components/schemas/TestCaseResponse' + TestCaseResponse: + type: object + properties: + version: + type: string + enum: + - '1' + description: The version of the test case response. + id: + type: string + format: uuid + description: The ID of the test case. + testTargetId: + type: string + format: uuid + type: + type: string + nullable: true + elements: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + interaction: + type: object + nullable: true + properties: + id: + type: string + format: uuid + action: + type: string + enum: + - EXTRACT + - ENTER_TEXT + - CLICK + - SELECT_OPTION + - TYPE_TEXT + - KEY_PRESS + - HOVER + - UPLOAD + - GO_TO + - DRAG_AND_DROP + - CLOSE_PAGE + - OPEN_EMAIL + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + assertion: + type: object + nullable: true + properties: + id: + type: string + format: uuid + expectation: + type: string + enum: + - VISIBLE + - NOT_VISIBLE + - TO_BE_CHECKED + - NOT_TO_BE_CHECKED + - TO_HAVE_VALUE + - TO_CONTAIN_TEXT + - TO_HAVE_STYLE + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + scrollState: + type: object + nullable: true + selectors: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + selector: + type: string + selectorType: + type: string + enum: + - TEXT + - LABEL + - PLACEHOLDER + - ROLE + options: + type: object + nullable: true + properties: + name: + type: string + testCaseElementId: + type: string + format: uuid + scrollStateId: + type: string + nullable: true + testCaseId: + type: string + format: uuid + ignoreFailure: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + description: + type: string + status: + type: string + enum: + - ENABLED + - DRAFT + externalId: + type: string + nullable: true + entryPointUrlPath: + type: string + nullable: true + tags: + type: array + items: + type: string + createdBy: + type: string + enum: + - EDIT + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + prerequisiteId: + type: string + nullable: true + proposalRunId: + type: string + nullable: true + folderId: + type: string + nullable: true + teardownTestCaseId: + type: string + nullable: true + discovery: + type: object + nullable: true + properties: + updatedAt: + type: string + format: date-time + freePrompt: + type: string + traceUrl: + type: string + nullable: true + status: + type: string + enum: + - CREATED + - IN_PROGRESS + - ERROR + - SUCCESS + - FAILED + - INCOMPLETE + - QUEUED + - STOPPED + - OUTDATED + - PAUSED + abortCause: + type: string + nullable: true + message: + type: string + nullable: true + required: + - id + - testTargetId + SuccessResponse: + type: object + properties: + success: + type: boolean + description: Indicates whether the operation was successful. + example: true + UnregisterRequest: + type: object + properties: + name: + type: string + RegisterRequest: + type: object + properties: + name: + type: string + registrationData: + type: object + properties: + proxypass: + type: string + example: secret22 + proxyuser: + type: string + example: user + address: + type: string + description: the address of the remote endpoint. IP and port + example: 34.45.23.22:23455 + EnvironmentsResponse: + type: array + items: + $ref: '#/components/schemas/EnvironmentResponse' + EnvironmentSimpleResponse: + type: object + properties: + id: + type: string + format: uuid + type: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + discoveryUrl: + type: string + format: url + example: https://example.com + email: + type: string + example: user@example.com + description: The 2FA email of the environment to test email flows. + EnvironmentResponse: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + testTargetId: + type: string + format: uuid + updatedAt: + type: string + format: date-time + type: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + discoveryUrl: + type: string + format: url + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + updatedAt: + type: string + format: date-time + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + updatedAt: + type: string + format: date-time + nullable: true + privateLocation: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + status: + type: string + type: + type: string + required: + - id + - name + - testTargetId + - type + CreateTestTargetBody: + type: object + properties: + testTarget: + type: object + properties: + app: + type: string + description: The app name or project name of the test target + discoveryUrl: + type: string + format: uri + description: The discovery URL of the test target. This is the URL that a discovery tool can use to discover the test target. + required: + - app + - discoveryUrl + TestTargetsResponse: + type: array + items: + $ref: '#/components/schemas/TestTargetResponse' + TestTargetResponse: + type: object + properties: + id: + type: string + format: uuid + app: + type: string + description: The app name or project name of the test target + tags: + type: array + items: + type: string + nullable: true + environments: + type: array + items: + $ref: '#/components/schemas/EnvironmentSimpleResponse' + description: The environments of the test target + required: + - id + - app + TestTargetUpdateRequest: + type: object + properties: + app: + type: string + nullable: true + description: The app name or project name of the test target + testIdAttribute: + type: string + nullable: true + description: The attribute name of the test ID + example: test-automation-id + testRailIntegration: + type: object + properties: + domain: + type: string + description: The domain of the TestRail instance + example: https://mycompany.testrail.io + username: + type: string + description: The username for the TestRail instance + example: user + projectId: + type: string + description: The project ID for the TestRail instance + example: '123' + apiKey: + type: string + description: The TestRail API key for the TestRail instance + example: '123123123' + nullable: true + timeoutPerStep: + type: number + format: int32 + minimum: 5000 + maximum: 30000 + description: The timeout per step in milliseconds + ZodResponse: + type: array + items: + type: object + properties: + code: + type: string + example: invalid_type + description: What error code happened while parsing the request + expected: + type: string + description: What the expected type was + example: object + received: + type: string + description: What the actual passed type was + example: string + path: + type: array + items: + type: string + description: The json path to the wrong parameter + example: key + message: + type: string + description: Human-readable message of the error that occurred while parsing. + example: Expected object, received string + ExternalBatchGenerationBody: + type: object + properties: + prompt: + type: string + description: Prompt to generate the test cases in this batch generation + imageUrls: + type: array + items: + type: string + description: Image URLs to generate the test cases in this batch generation + entryPointUrlPath: + type: string + nullable: true + description: Entry point URL path, where the batch generation will start + environmentId: + type: string + nullable: true + description: Environment ID, where the batch generation will be executed + prerequisiteId: + type: string + nullable: true + description: Prerequisite ID, which will be executed before the batch generation + baseUrl: + type: string + nullable: true + description: Base URL, where the batch generation will be executed + context: + $ref: '#/components/schemas/ExecutionContext' + BatchGenerationResponse: + type: object + properties: + batchGenerationId: + type: string + format: uuid + description: Unique identifier for the batch generation + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + Notification: + type: object + properties: + id: + type: string + format: uuid + description: Unique identifier for the event. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: The unique identifier of the test target this event belongs to. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: + type: string + format: date-time + description: The timestamp when the event was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the event was last updated. + example: '2024-09-06T13:01:51.686Z' + payload: + type: object + description: JSON payload containing event-specific data. + example: + testCaseId: 5deeef76-f37a-418b-8101-7d69529fa561 + type: + type: string + enum: + - VALIDATION_PASSED + - VALIDATION_FAILED + - DISCOVERY_FINISHED + - PROPOSAL_SUCCESS + - PROPOSAL_FAILED + - REPORT_EXECUTION_FINISHED + description: The type of event that occurred. + example: VALIDATION_PASSED + ack: + type: string + enum: + - IN_WEB_APP + description: Optional acknowledgment status of the event. + example: IN_WEB_APP + nullable: true + required: + - id + - testTargetId + - createdAt + - updatedAt + - payload + - type + ExternalDiscoveryBody: + type: object + properties: + name: + type: string + description: Name of the discovered test case + example: Login Form Validation Test + entryPointUrlPath: + type: string + description: Entry point URL path of the discovered test case + example: /login + prerequisiteName: + type: string + description: Prerequisite test case name + example: Cookie Banner Acceptance + externalId: + type: string + description: External ID of the discovered test case + example: ext-test-001 + tagNames: + type: array + items: + type: string + description: Tags to assign to the discovered test case + example: + - authentication + - forms + - validation + prompt: + type: string + description: Prompt to generate the discovered test case + example: Test the login form with valid and invalid credentials + folderName: + type: string + description: Folder name of the discovered test case + example: Authentication Tests + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + description: Type of the discovered test case + example: LOGIN + required: + - name + - prompt + DiscoveryResponse: + type: object + required: + - discoveryId + - testCaseId + properties: + discoveryId: + type: string + format: uuid + description: The ID of the created discovery + testCaseId: + type: string + format: uuid + description: The ID of the associated test case + ExecutionContext: + oneOf: + - type: object + properties: + source: + type: string + enum: + - github + example: github + issueNumber: + type: integer + nullable: true + example: 123 + ref: + type: string + nullable: true + example: refs/heads/main + sha: + type: string + nullable: true + example: abc123def456 + repo: + type: string + example: my-repo + owner: + type: string + example: repo-owner + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + nodeId: + type: string + nullable: true + example: node-123 + - type: object + properties: + source: + type: string + enum: + - azureDevOps + example: azureDevOps + accessToken: + type: string + example: token123 + organization: + type: string + example: my-org + project: + type: string + example: my-project + repositoryId: + type: string + example: repo-123 + sha: + type: string + nullable: true + example: abc123def456 + ref: + type: string + nullable: true + example: refs/heads/main + pullRequestId: + type: integer + nullable: true + example: 101 + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + threadId: + type: string + nullable: true + example: thread-123 + - type: object + properties: + source: + type: string + enum: + - discovery + example: discovery + description: + type: string + example: A discovery test + triggeredBy: + oneOf: + - type: object + properties: + type: + type: string + enum: + - INITIAL + example: INITIAL + - type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - manual + example: manual + description: + type: string + example: A manual trigger + triggeredBy: + type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - scheduled + example: scheduled + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - proposal + example: proposal + description: + type: string + example: A proposal trigger + triggeredBy: + oneOf: + - type: object + properties: + type: + type: string + enum: + - INITIAL + example: INITIAL + - type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + Variables: + type: object + additionalProperties: + type: array + items: + type: string + description: The variables to overwrite exclusively for this test run. + example: + SPACE_ID: + - 64ee32b4-7365-47a6-b5b0-2903b6ad849d + TestCaseElement: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + interaction: + type: object + nullable: true + properties: + id: + type: string + format: uuid + action: + type: string + enum: + - EXTRACT + - ENTER_TEXT + - CLICK + - SELECT_OPTION + - TYPE_TEXT + - KEY_PRESS + - HOVER + - UPLOAD + - GO_TO + - DRAG_AND_DROP + - CLOSE_PAGE + - OPEN_EMAIL + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + assertion: + type: object + nullable: true + properties: + id: + type: string + format: uuid + expectation: + type: string + enum: + - VISIBLE + - NOT_VISIBLE + - TO_BE_CHECKED + - NOT_TO_BE_CHECKED + - TO_HAVE_VALUE + - TO_CONTAIN_TEXT + - TO_HAVE_STYLE + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + selectors: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + selector: + type: string + selectorType: + type: string + enum: + - TEXT + - LABEL + - PLACEHOLDER + - ROLE + options: + type: object + nullable: true + testCaseElementId: + type: string + format: uuid + testCaseId: + type: string + format: uuid + ignoreFailure: + type: boolean + TestTargetSyncSchema: + title: TestTargetSyncSchema + description: schema for import and export of test cases + type: object + properties: + testCases: + type: array + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false + required: + - testCases + additionalProperties: false + TestTargetCodeSchema: + title: TestTargetCodeSchema + description: schema for export of test cases as code + type: object + properties: + executionUrl: + type: string + format: uri + environmentId: + type: string + format: uuid + testCases: + type: array + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false + required: + - executionUrl + - testCases + additionalProperties: false + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false diff --git a/package.json b/package.json index 548f3f3..efb94c0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dev:local": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.local.json OCTOMIND_API_URL=http://localhost:3000/api tsx src/index.ts", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.prod.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", - "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", + "apigen": "openapi-typescript openapi.yaml --output src/api.ts && orval", "build": "pnpm apigen && npx genversion -des src/version.ts && pnpm gendoc && tsc --project tsconfig.build.json", "octomind": "pnpm apigen && tsx src/index.ts", "test": "pnpm apigen && npx genversion -des src/version.ts && jest", @@ -41,6 +41,7 @@ "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", + "diff": "8.0.3", "open": "11.0.0", "openapi-fetch": "0.15.0", "otplib": "13.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 233de0e..0ffe79c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: commander: specifier: 14.0.2 version: 14.0.2 + diff: + specifier: 8.0.3 + version: 8.0.3 open: specifier: 11.0.0 version: 11.0.0 @@ -1572,6 +1575,10 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4907,6 +4914,8 @@ snapshots: diff@4.0.2: optional: true + diff@8.0.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 7963cbf..5c90c61 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,6 +1,6 @@ -import fs, { writeFileSync } from "fs"; -import path from "path"; +import fs from "fs"; +import { createTwoFilesPatch } from "diff"; import open from "open"; import yaml from "yaml"; @@ -72,6 +72,7 @@ const getTestCaseToEdit = async ( }, ); }; + export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -80,12 +81,10 @@ export const edit = async (options: EditOptions): Promise => { ); } - console.log({ octomindRoot }); const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath, }); - console.log({ testCaseFilePath }); if (!testCaseFilePath) { throw new Error( @@ -93,9 +92,10 @@ export const edit = async (options: EditOptions): Promise => { ); } + const originalTestCase = loadTestCase(testCaseFilePath); const testCaseToEdit = { - ...loadTestCase(testCaseFilePath), - localEditingStatus: "IN_PROGRESS", + ...originalTestCase, + localEditingStatus: "IN_PROGRESS" as const, }; const testCases = readTestCasesFromDir(octomindRoot); @@ -119,12 +119,10 @@ export const edit = async (options: EditOptions): Promise => { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const versionId = response?.versionIdByStableId[testCaseToEdit.id]; - + const versionId = response.versionIdByStableId[testCaseToEdit.id]; if (!versionId) { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const parsedBaseUrl = URL.parse(BASE_URL); const localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; await open(localEditingUrl); @@ -139,19 +137,44 @@ export const edit = async (options: EditOptions): Promise => { options, ); - while (localTestCase.data?.localEditingStatus === "IN_PROGRESS") { + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + + while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { await sleep(POLLING_INTERVAL); console.log("Waiting for local editing to finish..."); localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } } - await writeSingleTestCaseYaml(testCaseFilePath, { + const syncTestCaseWithoutExtraProperties: SyncTestCase & { + versionId: undefined; + } = { ...localTestCase.data, id: testCaseToEdit.id, localEditingStatus: undefined, versionId: undefined, - }); + }; - console.log("Edited test case successfully!"); + await writeSingleTestCaseYaml( + testCaseFilePath, + syncTestCaseWithoutExtraProperties, + ); + + const diff = createTwoFilesPatch( + "old.yaml", + "new.yaml", + yaml.stringify(originalTestCase), + yaml.stringify(syncTestCaseWithoutExtraProperties), + ); + console.log(`Edited test case successfully`); + console.log(diff); }; From c71a7f6597da3a2bd6428b34e2f16b6fda42729a Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 13 Jan 2026 19:45:06 +0100 Subject: [PATCH 17/36] throbber! --- package.json | 9 ++- pnpm-lock.yaml | 110 ++++++++++++++++++++++++++++++++ src/tools/yamlMutations/edit.ts | 94 ++++++++++++++++----------- 3 files changed, 174 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index efb94c0..3f5188d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,13 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", - "files": ["dist", "src", "README.md", "LICENSE", "package.json"], + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], "engines": { "node": ">=20.0.0" }, @@ -44,6 +50,7 @@ "diff": "8.0.3", "open": "11.0.0", "openapi-fetch": "0.15.0", + "ora": "9.0.0", "otplib": "13.0.2", "shell-quote": "1.8.3", "simple-git": "3.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ffe79c..f820aa1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: openapi-fetch: specifier: 0.15.0 version: 0.15.0 + ora: + specifier: 9.0.0 + version: 9.0.0 otplib: specifier: 13.0.2 version: 13.0.2 @@ -1419,6 +1422,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -1444,6 +1451,14 @@ packages: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-width@2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} @@ -1814,6 +1829,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2029,6 +2048,10 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2073,6 +2096,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -2384,6 +2411,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + loglevel-plugin-prefix@0.8.4: resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} @@ -2440,6 +2471,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2556,6 +2591,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -2578,6 +2617,10 @@ packages: openapi3-ts@4.5.0: resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} + ora@9.0.0: + resolution: {integrity: sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==} + engines: {node: '>=20'} + orval@7.18.0: resolution: {integrity: sha512-mPGSQeAAxhvRoxMKn22vmmtFa5eoJiwTBUSsrwKjKjC+rJdA3eWOLRiU1ICq2lep22S+3qpEy5WizP5FW26+rg==} engines: {node: '>=22.18.0'} @@ -2762,6 +2805,10 @@ packages: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2901,6 +2948,10 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2925,6 +2976,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@8.1.0: + resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + engines: {node: '>=20'} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -3286,6 +3341,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zod@4.3.5: resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} @@ -4789,6 +4848,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + change-case@5.4.4: {} char-regex@1.0.2: {} @@ -4807,6 +4868,12 @@ snapshots: dependencies: restore-cursor: 2.0.0 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + cli-width@2.2.1: {} cliui@8.0.1: @@ -5262,6 +5329,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5498,6 +5567,8 @@ snapshots: dependencies: is-docker: 3.0.0 + is-interactive@2.0.0: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -5539,6 +5610,8 @@ snapshots: dependencies: which-typed-array: 1.1.19 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -6040,6 +6113,11 @@ snapshots: lodash@4.17.21: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + loglevel-plugin-prefix@0.8.4: {} loglevel@1.9.2: {} @@ -6088,6 +6166,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -6210,6 +6290,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@11.0.0: dependencies: default-browser: 5.4.0 @@ -6241,6 +6325,18 @@ snapshots: dependencies: yaml: 2.8.2 + ora@9.0.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.2.2 + string-width: 8.1.0 + strip-ansi: 7.1.2 + orval@7.18.0(openapi-types@12.1.3)(typescript@5.9.3): dependencies: '@apidevtools/swagger-parser': 12.1.0(openapi-types@12.1.3) @@ -6447,6 +6543,11 @@ snapshots: onetime: 2.0.1 signal-exit: 3.0.7 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.1.0: {} run-applescript@7.1.0: {} @@ -6605,6 +6706,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stdin-discarder@0.2.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -6634,6 +6737,11 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@8.1.0: + dependencies: + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -7050,4 +7158,6 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + zod@4.3.5: {} diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 5c90c61..b2b1d89 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,7 +1,8 @@ import fs from "fs"; -import { createTwoFilesPatch } from "diff"; +import { createTwoFilesPatch, parsePatch } from "diff"; import open from "open"; +import ora from "ora"; import yaml from "yaml"; import { OCTOMIND_FOLDER_NAME } from "../../constants"; @@ -54,9 +55,9 @@ const loadTestCase = (testCasePath: string): SyncTestCase => { }; const POLLING_INTERVAL = 1000; -const getTestCaseToEdit = async ( +const getTestCaseVersion = async ( versionId: string, - testCaseToEdit: SyncTestCase, + testCase: SyncTestCase, options: EditOptions, ) => { return await client.GET( @@ -65,7 +66,7 @@ const getTestCaseToEdit = async ( params: { path: { versionId, - testCaseId: testCaseToEdit.id, + testCaseId: testCase.id, testTargetId: options.testTargetId, }, }, @@ -73,6 +74,53 @@ const getTestCaseToEdit = async ( ); }; +const waitForLocalEditingToBeFinished = async ( + versionId: string, + testCaseToEdit: SyncTestCase, + options: EditOptions, +): Promise => { + let localTestCase = await getTestCaseVersion( + versionId, + testCaseToEdit, + options, + ); + + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + + const throbber = ora("Waiting for local editing to finish..").start(); + while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { + await sleep(POLLING_INTERVAL); + + localTestCase = await getTestCaseVersion( + versionId, + testCaseToEdit, + options, + ); + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + } + + throbber.succeed(); + + const syncTestCaseWithoutExtraProperties: SyncTestCase & { + versionId: undefined; + } = { + ...localTestCase.data, + id: testCaseToEdit.id, + localEditingStatus: undefined, + versionId: undefined, + }; + + return syncTestCaseWithoutExtraProperties; +}; + export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -101,7 +149,6 @@ export const edit = async (options: EditOptions): Promise => { const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); - checkForConsistency(relevantTestCases); const response = await draftPush( @@ -131,50 +178,21 @@ export const edit = async (options: EditOptions): Promise => { `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); - let localTestCase = await getTestCaseToEdit( + const editResult = await waitForLocalEditingToBeFinished( versionId, testCaseToEdit, options, ); - if (!localTestCase.data) { - throw new Error( - `Could not get local editing status for test case ${testCaseToEdit.id}`, - ); - } - - while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { - await sleep(POLLING_INTERVAL); - console.log("Waiting for local editing to finish..."); - - localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); - if (!localTestCase.data) { - throw new Error( - `Could not get local editing status for test case ${testCaseToEdit.id}`, - ); - } - } - - const syncTestCaseWithoutExtraProperties: SyncTestCase & { - versionId: undefined; - } = { - ...localTestCase.data, - id: testCaseToEdit.id, - localEditingStatus: undefined, - versionId: undefined, - }; - - await writeSingleTestCaseYaml( - testCaseFilePath, - syncTestCaseWithoutExtraProperties, - ); + await writeSingleTestCaseYaml(testCaseFilePath, editResult); const diff = createTwoFilesPatch( "old.yaml", "new.yaml", yaml.stringify(originalTestCase), - yaml.stringify(syncTestCaseWithoutExtraProperties), + yaml.stringify(editResult), ); + console.log(`Edited test case successfully`); console.log(diff); }; From d3054290535716a8f0b24a635d16439bc87ea927 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 13 Jan 2026 19:52:04 +0100 Subject: [PATCH 18/36] add cancellation, better throbber exiting --- src/tools/yamlMutations/edit.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b2b1d89..8525844 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -78,7 +78,7 @@ const waitForLocalEditingToBeFinished = async ( versionId: string, testCaseToEdit: SyncTestCase, options: EditOptions, -): Promise => { +): Promise => { let localTestCase = await getTestCaseVersion( versionId, testCaseToEdit, @@ -91,7 +91,7 @@ const waitForLocalEditingToBeFinished = async ( ); } - const throbber = ora("Waiting for local editing to finish..").start(); + const throbber = ora("Waiting for editing to finish in UI").start(); while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { await sleep(POLLING_INTERVAL); @@ -105,9 +105,14 @@ const waitForLocalEditingToBeFinished = async ( `Could not get local editing status for test case ${testCaseToEdit.id}`, ); } + + if (localTestCase.data.localEditingStatus === "CANCELLED") { + throbber.fail("cancelled by user"); + return "cancelled"; + } } - throbber.succeed(); + throbber.succeed("Finished editing in UI"); const syncTestCaseWithoutExtraProperties: SyncTestCase & { versionId: undefined; @@ -184,6 +189,11 @@ export const edit = async (options: EditOptions): Promise => { options, ); + if (editResult === "cancelled") { + console.log("Cancelled editing test case, exiting"); + return; + } + await writeSingleTestCaseYaml(testCaseFilePath, editResult); const diff = createTwoFilesPatch( From 3a6c9a757a5db4a2392e6b0f49b83c241411b6a1 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 10:38:52 +0100 Subject: [PATCH 19/36] remove import --- src/tools/yamlMutations/edit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 8525844..042dae5 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,6 +1,6 @@ import fs from "fs"; -import { createTwoFilesPatch, parsePatch } from "diff"; +import { createTwoFilesPatch } from "diff"; import open from "open"; import ora from "ora"; import yaml from "yaml"; From 8d660d7dd451b035bf476248b48e164692fb0393 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 14:35:31 +0100 Subject: [PATCH 20/36] Relies on new draft push results, shows test results in edit view. --- src/tools/sync/push.ts | 4 ++-- src/tools/sync/types.ts | 4 +++- src/tools/yamlMutations/edit.ts | 9 +++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/tools/sync/push.ts b/src/tools/sync/push.ts index fe8d689..54d605e 100644 --- a/src/tools/sync/push.ts +++ b/src/tools/sync/push.ts @@ -4,7 +4,7 @@ import { components, paths } from "../../api"; import { ListOptions } from "../client"; import { checkForConsistency } from "./consistency"; import { getGitContext } from "./git"; -import { TestTargetSyncData } from "./types"; +import { SyncDataByStableId, TestTargetSyncData } from "./types"; import { readTestCasesFromDir } from "./yml"; type ErrorResponse = @@ -72,7 +72,7 @@ export const draftPush = async ( | { success: boolean; versionIds: string[]; - versionIdByStableId: Record; + syncDataByStableId: SyncDataByStableId; } | undefined > => { diff --git a/src/tools/sync/types.ts b/src/tools/sync/types.ts index 89d5ee8..0629d8e 100644 --- a/src/tools/sync/types.ts +++ b/src/tools/sync/types.ts @@ -1,6 +1,8 @@ -import { components } from "../../api"; +import { components, operations } from "../../api"; export type TestTargetSyncData = components["schemas"]["TestTargetSyncSchema"]; export type SyncTestCases = TestTargetSyncData["testCases"]; export type SyncTestCase = SyncTestCases[number]; export type ExecutionContext = components["schemas"]["ExecutionContext"]; +export type SyncDataByStableId = + operations["pushTestTargetDraft"]["responses"]["200"]["content"]["application/json"]["syncDataByStableId"]; diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 042dae5..b894d0f 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -171,12 +171,17 @@ export const edit = async (options: EditOptions): Promise => { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const versionId = response.versionIdByStableId[testCaseToEdit.id]; + const { versionId, testResultId } = + response.syncDataByStableId[testCaseToEdit.id]; if (!versionId) { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } const parsedBaseUrl = URL.parse(BASE_URL); - const localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; + let localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; + if (testResultId) { + localEditingUrl += `&testResultId=${testResultId}`; + } + await open(localEditingUrl); console.log( From caacd59ccacaf5edd60292d28414de8247ace9ad Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Mon, 12 Jan 2026 17:35:10 +0100 Subject: [PATCH 21/36] add edit CLI command --- src/cli.ts | 35 +++++++++----- src/tools/yamlMutations/edit.ts | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 src/tools/yamlMutations/edit.ts diff --git a/src/cli.ts b/src/cli.ts index 3ffc061..b94e207 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -39,6 +39,7 @@ import { import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; import { version } from "./version"; +import { edit } from "./tools/yamlMutations/edit"; export const BINARY_NAME = "octomind"; @@ -49,17 +50,17 @@ type WithTestTargetId = { testTargetId: string }; const addTestTargetWrapper = (fn: (options: T) => Promise) => - async ( - options: Omit & Partial>, - ): Promise => { - const resolvedTestTargetId = await resolveTestTargetId( - options.testTargetId, - ); - await fn({ - ...options, - testTargetId: resolvedTestTargetId, - } as T); - }; + async ( + options: Omit & Partial>, + ): Promise => { + const resolvedTestTargetId = await resolveTestTargetId( + options.testTargetId, + ); + await fn({ + ...options, + testTargetId: resolvedTestTargetId, + } as T); + }; const testTargetIdOption = new Option( "-t, --test-target-id [id]", @@ -401,6 +402,18 @@ export const buildCmd = (): CompletableCommand => { .addOption(testTargetIdOption) .action(addTestTargetWrapper(pushTestTarget)); + // noinspection RequiredAttributes + createCommandWithCommonOptions(program, "edit-test-case") + .completer(testTargetIdCompleter) + .description("Edit yaml test case") + .helpGroup("test-cases") + .addOption(testTargetIdOption) + .requiredOption( + "-f, --file-path ", + "The path to the local yaml file you want to edit", + ) + .action(addTestTargetWrapper(edit)); + createCommandWithCommonOptions(program, "list-test-targets") .description("List all test targets") .helpGroup("test-targets") diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts new file mode 100644 index 0000000..09ee3e5 --- /dev/null +++ b/src/tools/yamlMutations/edit.ts @@ -0,0 +1,84 @@ +import fs from "fs"; +import path from "path"; + +import yaml from "yaml"; + +import { client, handleError } from "../client"; +import { checkForConsistency } from "../sync/consistency"; +import { draftPush } from "../sync/push"; +import { SyncTestCase } from "../sync/types"; +import { readTestCasesFromDir } from "../sync/yml"; +import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; + +type EditOptions = { + testTargetId: string; + filePath: string; +}; + + +const getRelevantTestCases = ( + testCasesById: Record, + startTestCase: SyncTestCase, +): SyncTestCase[] => { + let dependencyId = startTestCase.dependencyId; + const result: SyncTestCase[] = [startTestCase]; + + while (dependencyId) { + const currentTestCase = testCasesById[dependencyId]; + + if (!currentTestCase) { + throw new Error( + `Could not find dependency ${dependencyId} for ${startTestCase.id}`, + ); + } + + result.push(currentTestCase); + dependencyId = currentTestCase?.dependencyId; + } + + return result; +}; + +const loadTestCase = (testCasePath: string): SyncTestCase => { + try { + const content = fs.readFileSync(testCasePath, "utf8"); + return yaml.parse(content); + } catch (error) { + throw new Error(`Could not parse ${testCasePath}: ${error}`); + } +}; + +export const edit = async (options: EditOptions): Promise => { + const octomindRoot = await findOctomindFolder(); + if (!octomindRoot) { + throw new Error( + `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, + ); + } + const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + if (!testCaseFilePath) { + throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) + } + + const testCaseToEdit = loadTestCase(testCaseFilePath); + + const testCases = readTestCasesFromDir(octomindRoot); + const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); + const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); + + checkForConsistency(relevantTestCases); + + const response = await draftPush( + { + testCases: relevantTestCases, + }, + { + testTargetId: options.testTargetId, + client, + onError: handleError, + }, + ); + + console.log(response); +}; From bba3b534073b7fac751b7a598cf0a10a1eca0161 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:02:43 +0100 Subject: [PATCH 22/36] fix linting --- src/cli.ts | 24 ++++++++++++------------ src/tools/yamlMutations/edit.ts | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index b94e207..ed6e970 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,8 +38,8 @@ import { } from "./tools"; import { init, switchTestTarget } from "./tools/init"; import { update } from "./tools/update"; -import { version } from "./version"; import { edit } from "./tools/yamlMutations/edit"; +import { version } from "./version"; export const BINARY_NAME = "octomind"; @@ -50,17 +50,17 @@ type WithTestTargetId = { testTargetId: string }; const addTestTargetWrapper = (fn: (options: T) => Promise) => - async ( - options: Omit & Partial>, - ): Promise => { - const resolvedTestTargetId = await resolveTestTargetId( - options.testTargetId, - ); - await fn({ - ...options, - testTargetId: resolvedTestTargetId, - } as T); - }; + async ( + options: Omit & Partial>, + ): Promise => { + const resolvedTestTargetId = await resolveTestTargetId( + options.testTargetId, + ); + await fn({ + ...options, + testTargetId: resolvedTestTargetId, + } as T); + }; const testTargetIdOption = new Option( "-t, --test-target-id [id]", diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 09ee3e5..689c309 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -3,20 +3,22 @@ import path from "path"; import yaml from "yaml"; +import { OCTOMIND_FOLDER_NAME } from "../../constants"; +import { + findOctomindFolder, + getAbsoluteFilePathInOctomindRoot, +} from "../../helpers"; import { client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir } from "../sync/yml"; -import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot } from "../../helpers"; -import { OCTOMIND_FOLDER_NAME } from "../../constants"; type EditOptions = { testTargetId: string; filePath: string; }; - const getRelevantTestCases = ( testCasesById: Record, startTestCase: SyncTestCase, @@ -56,9 +58,14 @@ export const edit = async (options: EditOptions): Promise => { `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } - const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath }) + const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ + octomindRoot, + filePath: options.filePath, + }); if (!testCaseFilePath) { - throw new Error(`Could not find ${options.filePath} in folder ${octomindRoot}`) + throw new Error( + `Could not find ${options.filePath} in folder ${octomindRoot}`, + ); } const testCaseToEdit = loadTestCase(testCaseFilePath); From 21b3a4595a895624eb1a3439cbde306a40c36836 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Tue, 13 Jan 2026 17:58:09 +0100 Subject: [PATCH 23/36] fix test case path --- src/helpers.ts | 13 ++++++------- tests/helpers.spec.ts | 42 ++++-------------------------------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 06fd58e..c6cce4d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -68,12 +68,11 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ filePath: string; octomindRoot: string; }): Promise => { - try { - const resolvedPath = await fsPromises.realpath( - path.isAbsolute(filePath) ? filePath : path.join(octomindRoot, filePath), - ); - return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; - } catch { - return null; + let resolvedPath: string + if (path.isAbsolute(filePath)) { + resolvedPath = filePath + } else { + resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)) } + return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; }; diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index b09dff3..4c76cde 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -109,34 +109,29 @@ describe("helpers", () => { }); it("should resolve relative path within octomind root", async () => { - const filePath = path.join(octomindRoot, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); + const expectedPath = path.join(octomindRoot, "test-case.yaml"); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "test-case.yaml", octomindRoot, }); - expect(result).toBe(filePath); + expect(result).toBe(expectedPath); }); it("should resolve nested relative path within octomind root", async () => { - const subdir = path.join(octomindRoot, "subdir"); - await fsPromises.mkdir(subdir); - const filePath = path.join(subdir, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); + const expectedPath = path.join(octomindRoot, "subdir", "test-case.yaml"); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "subdir/test-case.yaml", octomindRoot, }); - expect(result).toBe(filePath); + expect(result).toBe(expectedPath); }); it("should accept absolute path within octomind root", async () => { const filePath = path.join(octomindRoot, "test-case.yaml"); - await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath, @@ -148,7 +143,6 @@ describe("helpers", () => { it("should return null for absolute path outside octomind root", async () => { const outsideFile = path.join(tmpDir, "outside.yaml"); - await fsPromises.writeFile(outsideFile, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: outsideFile, @@ -159,9 +153,6 @@ describe("helpers", () => { }); it("should return null for path traversal attempts", async () => { - const outsideFile = path.join(tmpDir, "outside.yaml"); - await fsPromises.writeFile(outsideFile, ""); - const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "../outside.yaml", octomindRoot, @@ -170,30 +161,5 @@ describe("helpers", () => { expect(result).toBeNull(); }); - it("should return null for non-existent file", async () => { - const result = await getAbsoluteFilePathInOctomindRoot({ - filePath: "does-not-exist.yaml", - octomindRoot, - }); - - expect(result).toBeNull(); - }); - - it("should return null for malformed path with nested octomind structure", async () => { - // e.g. /some/folder/.octomind/some/folder/.octomind/a.yaml - const malformedPath = path.join( - octomindRoot, - tmpDir, - OCTOMIND_FOLDER_NAME, - "a.yaml", - ); - - const result = await getAbsoluteFilePathInOctomindRoot({ - filePath: malformedPath, - octomindRoot, - }); - - expect(result).toBeNull(); - }); }); }); From 2526bb2b3dfe652158b3345844628cec6d35c58d Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Tue, 13 Jan 2026 18:17:20 +0100 Subject: [PATCH 24/36] absolute files in octomind root --- src/helpers.ts | 31 ++++++++++++++++++++++++++----- tests/helpers.spec.ts | 22 +++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index c6cce4d..a3e11d3 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -68,11 +68,32 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ filePath: string; octomindRoot: string; }): Promise => { - let resolvedPath: string + const isWithinOctomindRoot = (p: string) => p.startsWith(octomindRoot); + + const isFile = async (p: string): Promise => { + try { + const stats = await fsPromises.stat(p); + return stats.isFile(); + } catch { + return false; + } + }; + if (path.isAbsolute(filePath)) { - resolvedPath = filePath - } else { - resolvedPath = path.resolve(filePath, path.join(octomindRoot, filePath)) + return isWithinOctomindRoot(filePath) ? filePath : null; } - return resolvedPath.startsWith(octomindRoot) ? resolvedPath : null; + + // For relative paths, try resolving from cwd first, then from octomindRoot + const candidates = [ + path.resolve(filePath), + path.resolve(octomindRoot, filePath), + ]; + + for (const candidate of candidates) { + if (isWithinOctomindRoot(candidate) && (await isFile(candidate))) { + return candidate; + } + } + + return null; }; diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index 4c76cde..cd1c92a 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -109,25 +109,38 @@ describe("helpers", () => { }); it("should resolve relative path within octomind root", async () => { - const expectedPath = path.join(octomindRoot, "test-case.yaml"); + const filePath = path.join(octomindRoot, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "test-case.yaml", octomindRoot, }); - expect(result).toBe(expectedPath); + expect(result).toBe(filePath); }); it("should resolve nested relative path within octomind root", async () => { - const expectedPath = path.join(octomindRoot, "subdir", "test-case.yaml"); + const subdir = path.join(octomindRoot, "subdir"); + await fsPromises.mkdir(subdir); + const filePath = path.join(subdir, "test-case.yaml"); + await fsPromises.writeFile(filePath, ""); const result = await getAbsoluteFilePathInOctomindRoot({ filePath: "subdir/test-case.yaml", octomindRoot, }); - expect(result).toBe(expectedPath); + expect(result).toBe(filePath); + }); + + it("should return null for non-existent relative path", async () => { + const result = await getAbsoluteFilePathInOctomindRoot({ + filePath: "does-not-exist.yaml", + octomindRoot, + }); + + expect(result).toBeNull(); }); it("should accept absolute path within octomind root", async () => { @@ -160,6 +173,5 @@ describe("helpers", () => { expect(result).toBeNull(); }); - }); }); From 8ef982774150a94c4427f6d85053b2982f5c7346 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:02:58 +0100 Subject: [PATCH 25/36] add open dependency, finish editing implementation --- README.md | 14 ++++++ package.json | 1 + src/helpers.ts | 3 ++ src/tools/sync/push.ts | 9 +++- src/tools/sync/yml.ts | 20 +++++++-- src/tools/test-targets.ts | 2 +- src/tools/yamlMutations/edit.ts | 76 ++++++++++++++++++++++++++++++--- 7 files changed, 115 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 64dc2e2..15ff7a8 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,20 @@ Push local YAML test cases to the test target | `-j, --json` | Output raw JSON response | No | | | `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | | +## edit-test-case + +Edit yaml test case + +**Usage:** `edit-test-case [options]` + +### Options + +| Option | Description | Required | Default | +|:-------|:----------|:---------|:--------| +| `-j, --json` | Output raw JSON response | No | | +| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | | +| `-f, --file-path ` | The path to the local yaml file you want to edit | Yes | | + ## Test Reports ## test-report diff --git a/package.json b/package.json index a773845..c18568a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", + "open": "11.0.0", "openapi-fetch": "0.15.0", "otplib": "13.1.0", "shell-quote": "1.8.3", diff --git a/src/helpers.ts b/src/helpers.ts index a3e11d3..11b7b80 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -97,3 +97,6 @@ export const getAbsoluteFilePathInOctomindRoot = async ({ return null; }; + +export const sleep = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/tools/sync/push.ts b/src/tools/sync/push.ts index 173f548..fe8d689 100644 --- a/src/tools/sync/push.ts +++ b/src/tools/sync/push.ts @@ -68,7 +68,14 @@ const defaultPush = async ( export const draftPush = async ( body: TestTargetSyncData, options: Omit & ListOptions, -): Promise<{ success: boolean; versionIds: string[] } | undefined> => { +): Promise< + | { + success: boolean; + versionIds: string[]; + versionIdByStableId: Record; + } + | undefined +> => { const { data, error } = await options.client.POST( "/apiKey/beta/test-targets/{testTargetId}/draft/push", { diff --git a/src/tools/sync/yml.ts b/src/tools/sync/yml.ts index f0aa73e..e0d28db 100644 --- a/src/tools/sync/yml.ts +++ b/src/tools/sync/yml.ts @@ -1,4 +1,5 @@ import fs from "fs"; +import fsPromises from "fs/promises"; import path from "path"; import yaml from "yaml"; @@ -40,7 +41,20 @@ const toFileSystemCompatibleCamelCase = (description: string): string => { return camelCased; }; -export const writeYaml = (data: TestTargetSyncData, destination?: string) => { +export const writeSingleTestCaseYaml = async ( + filePath: string, + testCase: SyncTestCase, +): Promise => { + return fsPromises.writeFile( + filePath, + `# yaml-language-server: $schema=https://app.octomind.dev/schemas/SyncTestCaseSchema.json\n${yaml.stringify(testCase)}`, + ); +}; + +export const writeYaml = async ( + data: TestTargetSyncData, + destination?: string, +): Promise => { cleanupFilesystem({ newTestCases: data.testCases, destination, @@ -50,9 +64,9 @@ export const writeYaml = (data: TestTargetSyncData, destination?: string) => { const folderName = buildFolderName(testCase, data.testCases, destination); const testCaseFilename = buildFilename(testCase, folderName); fs.mkdirSync(folderName, { recursive: true }); - fs.writeFileSync( + await writeSingleTestCaseYaml( path.join(folderName, testCaseFilename), - `# yaml-language-server: $schema=https://app.octomind.dev/schemas/SyncTestCaseSchema.json\n${yaml.stringify(testCase)}`, + testCase, ); } }; diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index 77b185f..ada58cb 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -73,7 +73,7 @@ export const pullTestTarget = async ( const destination = (await findOctomindFolder()) ?? path.join(process.cwd(), OCTOMIND_FOLDER_NAME); - writeYaml(data, destination); + await writeYaml(data, destination); console.log("Test Target pulled successfully"); }; diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 689c309..7963cbf 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,18 +1,20 @@ -import fs from "fs"; +import fs, { writeFileSync } from "fs"; import path from "path"; +import open from "open"; import yaml from "yaml"; import { OCTOMIND_FOLDER_NAME } from "../../constants"; import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, + sleep, } from "../../helpers"; -import { client, handleError } from "../client"; +import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; -import { readTestCasesFromDir } from "../sync/yml"; +import { readTestCasesFromDir, writeSingleTestCaseYaml } from "../sync/yml"; type EditOptions = { testTargetId: string; @@ -51,6 +53,25 @@ const loadTestCase = (testCasePath: string): SyncTestCase => { } }; +const POLLING_INTERVAL = 1000; +const getTestCaseToEdit = async ( + versionId: string, + testCaseToEdit: SyncTestCase, + options: EditOptions, +) => { + return await client.GET( + "/apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}", + { + params: { + path: { + versionId, + testCaseId: testCaseToEdit.id, + testTargetId: options.testTargetId, + }, + }, + }, + ); +}; export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -58,17 +79,24 @@ export const edit = async (options: EditOptions): Promise => { `Could not find ${OCTOMIND_FOLDER_NAME} folder, make sure to pull before trying to edit`, ); } + + console.log({ octomindRoot }); const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath, }); + console.log({ testCaseFilePath }); + if (!testCaseFilePath) { throw new Error( `Could not find ${options.filePath} in folder ${octomindRoot}`, ); } - const testCaseToEdit = loadTestCase(testCaseFilePath); + const testCaseToEdit = { + ...loadTestCase(testCaseFilePath), + localEditingStatus: "IN_PROGRESS", + }; const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); @@ -87,5 +115,43 @@ export const edit = async (options: EditOptions): Promise => { }, ); - console.log(response); + if (!response) { + throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); + } + + const versionId = response?.versionIdByStableId[testCaseToEdit.id]; + + if (!versionId) { + throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); + } + + const parsedBaseUrl = URL.parse(BASE_URL); + const localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; + await open(localEditingUrl); + + console.log( + `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, + ); + + let localTestCase = await getTestCaseToEdit( + versionId, + testCaseToEdit, + options, + ); + + while (localTestCase.data?.localEditingStatus === "IN_PROGRESS") { + await sleep(POLLING_INTERVAL); + console.log("Waiting for local editing to finish..."); + + localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); + } + + await writeSingleTestCaseYaml(testCaseFilePath, { + ...localTestCase.data, + id: testCaseToEdit.id, + localEditingStatus: undefined, + versionId: undefined, + }); + + console.log("Edited test case successfully!"); }; From aadd2739caec5dcfb9d5273161f0b3b09847d845 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:03:07 +0100 Subject: [PATCH 26/36] working version --- openapi.yaml | 4735 +++++++++++++++++++++++++++++++ package.json | 3 +- src/tools/yamlMutations/edit.ts | 49 +- 3 files changed, 4773 insertions(+), 14 deletions(-) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..e3774b0 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,4735 @@ +openapi: 3.1.0 +info: + title: Octomind external API + description: Octomind API that allows you to execute test cases, fetch reports and register private location workers by providing a URL and an ID. + version: 1.0.0 + contact: + name: API Support + url: https://octomind.dev/docs + email: support@octomind.dev +servers: + - url: https://app.octomind.dev/api + description: Main API Endpoint +tags: + - name: Test targets + description: Operations for managing test targets + - name: Execute + description: Operations for executing tests + - name: Environments + description: Operations for managing test environments + - name: Reports + description: Operations for retrieving test reports + - name: Notifications + description: Operations for retrieving notifications + - name: Private locations + description: Operations for managing private locations + - name: Test Cases + description: Operations for retrieving test cases + - name: Discoveries + description: Operations for creating test discoveries +externalDocs: + description: Find out more + url: https://octomind.dev/docs/ +paths: + /apiKey/v3/test-targets: + get: + summary: Retrieve all test targets + description: Gets a list of test targets. + operationId: getTestTargets + tags: + - Test targets + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetsResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + post: + summary: Create a new test target + description: Creates a new test target. + operationId: createTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTestTargetBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}: + get: + summary: Retrieve a test target + description: Gets a test target by ID. + operationId: getTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to fetch + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + patch: + summary: Update a test target + description: Updates a test target by ID. + operationId: updateTestTarget + tags: + - Test targets + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to update + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetUpdateRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + delete: + summary: Delete a test target + description: Deletes a test target by ID. + operationId: deleteTestTarget + tags: + - Test targets + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to delete + responses: + '204': + description: OK + '401': + description: Invalid or missing API key + '404': + description: Test target not found + '500': + description: Internal server error + /apiKey/v3/execute: + post: + summary: Execute tests of the given test target + description: | + This endpoint triggers a test execution by sending an test target id, an URL and optionally tags, an environment and variables. + operationId: executeTests + tags: + - Execute + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetExecutionRequest' + responses: + '200': + description: Test executed successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestExecutionResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/batch-generations: + post: + summary: Create a batch generation + description: Creates a batch generation for the given test target. + operationId: createBatchGeneration + tags: + - Discoveries + security: + - ApiKeyAuth: [] + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalBatchGenerationBody' + responses: + '200': + description: Batch generation created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGenerationResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/config: + get: + summary: Retrieve test target configuration + description: Get the test target configuration for a specific environment + operationId: getTestTargetConfig + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: url + in: query + required: true + schema: + type: string + format: uri + description: The execution URL for the test target + - name: outputDir + in: query + required: true + schema: + type: string + description: The directory where test output will be stored + - name: headless + in: query + required: false + schema: + type: string + description: Whether to run tests in headless mode (true/false) + - name: environmentId + in: query + required: false + schema: + type: string + format: uuid + description: Optional ID of the environment to use (defaults to the test target's default environment if not provided) + responses: + '200': + description: Test target configuration retrieved successfully + content: + text/plain: + schema: + type: string + description: The test target configuration as plain text + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Environment not found + '405': + description: Method not allowed + /apiKey/v3/test-targets/{testTargetId}/environments: + post: + summary: Create an environment + description: Create a custom environment. + operationId: createEnvironment + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the environment belongs to + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + discoveryUrl: + type: string + format: url + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + nullable: true + privateLocationName: + type: string + description: name of the private location + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + responses: + '201': + description: environment created + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + get: + summary: Retrieve environments + description: get a list of all defined environments. + operationId: getEnvironments + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + responses: + '200': + description: environments + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentsResponse' + /apiKey/v3/test-targets/{testTargetId}/environments/{environmentId}: + patch: + summary: Update an environment + description: Updates an enviroment, all properties can be set separately + operationId: updateEnvironment + security: + - ApiKeyAuth: [] + tags: + - Environments + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the environment belongs to + - in: path + name: environmentId + required: true + schema: + type: string + format: uuid + description: ID of the environment to update + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + nullable: true + discoveryUrl: + type: string + format: url + nullable: true + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + nullable: true + privateLocationName: + type: string + nullable: true + description: name of the private location + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + responses: + '200': + description: Environment updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/EnvironmentResponse' + delete: + summary: Delete an environment + operationId: deleteEnvironment + description: deletes an enviroment. this operation is not reversable. + security: + - ApiKeyAuth: [] + tags: + - Environments + responses: + '200': + description: Environment deleted successfully + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + - in: path + name: environmentId + required: true + schema: + type: string + format: uuid + description: ID of the environment to update + /apiKey/v3/test-targets/{testTargetId}/test-reports/{testReportId}: + get: + summary: Retrieve information about a test report + description: Poll from within a CI-pipeline to wait for the completion of a report. + operationId: getTestReport + tags: + - Reports + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target to which the test report belongs to + - in: path + name: testReportId + required: true + schema: + type: string + format: uuid + description: ID of the test report to fetch + security: + - ApiKeyAuth: [] + responses: + '200': + description: Test Report information + content: + application/json: + schema: + $ref: '#/components/schemas/TestReport' + example: + id: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:05:23.412Z' + executionUrl: https://en.wikipedia.org/ + status: FAILED + context: + source: github + issueNumber: 123 + ref: refs/heads/main + sha: abc123def456 + repo: my-repo + owner: repo-owner + triggeredBy: + type: USER + userId: 3435918b-3d29-4ebd-8c68-9a540532f45a + nodeId: node-123 + testResults: + - id: 9876fedc-ba98-7654-3210-fedcba987654 + testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:03:12.345Z' + status: PASSED + errorMessage: null + traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip + - id: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: 5432fedc-ba98-7654-3210-fedcba543210 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:04:45.678Z' + status: FAILED + errorMessage: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' + traceUrl: https://storage.googleapis.com/automagically-traces/2a3b4c5d-6e7f-8g9h-0i1j-2k3l4m5n6o7p-trace.zip + - id: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: 1234abcd-ef56-7890-abcd-ef1234567890 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:05:23.412Z' + status: WAITING + errorMessage: null + traceUrl: null + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-reports: + get: + summary: Retrieve paginated information about test reports + description: Allow fetching the history of test reports for your test target. + operationId: getTestReports + tags: + - Reports + parameters: + - in: path + name: testTargetId + required: true + schema: + type: string + format: uuid + description: ID of the test target for which to fetch the history for + - in: query + name: key + required: false + schema: + type: object + properties: + createdAt: + type: string + format: date-time + description: The timestamp of the key of the next page to fetch - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) + example: '2024-09-06T13:01:51.686Z' + - in: query + name: filter + required: false + schema: + type: array + items: + type: object + properties: + key: + type: string + description: The name of the property to filter for, e.g. an environmentId + example: environmentId + operator: + type: string + enum: + - EQUALS + description: How to compare the property in question, only EQUALS is supported so far. + value: + type: string + format: uuid + description: The value to compare with to find matches. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + security: + - ApiKeyAuth: [] + responses: + '200': + description: Test Reports information + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/TestReport' + key: + type: object + properties: + createdAt: + type: string + format: date-time + description: The timestamp of the key of the next page to fetch - use this in the query when fetching the next page of reports - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) + example: '2024-09-06T13:01:51.686Z' + hasNextPage: + type: boolean + description: If the query in question has another page to retrieve + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Invalid or missing API key + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/notifications: + get: + summary: Retrieve notifications + description: Get a list of notifications for a specific test target. + operationId: getNotifications + security: + - ApiKeyAuth: [] + tags: + - Notifications + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + responses: + '200': + description: List of notifications + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Notification' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/v1/private-location: + get: + security: + - ApiKeyAuth: [] + summary: Retrieve all private locations + description: gets a list of private location workers + operationId: getPrivateLocations + tags: + - Private locations + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PrivateLocationInfo' + /apiKey/v1/private-location/register: + put: + security: + - ApiKeyAuth: [] + summary: Register a private location + description: registers a private location worker + operationId: registerPrivateLocation + tags: + - Private locations + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '404': + description: private location of that name not found + '500': + description: Internal server error + /apiKey/v1/private-location/unregister: + put: + security: + - ApiKeyAuth: [] + summary: Unregister a private location + description: Unregisters a private location worker. + operationId: unregisterPrivateLocation + tags: + - Private locations + externalDocs: + url: https://octomind.dev/docs/proxy/private-location + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnregisterRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '404': + description: private location of that name not found + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-cases: + get: + summary: List test cases + description: Get a list of test cases for a specific test target with optional filtering + operationId: getTestCases + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: filter + in: query + required: false + schema: + type: string + description: | + JSON string containing filter criteria for test cases. The filter supports the following fields: + - testTargetId: Filter by test target ID + - description: Filter by test case description + - status: Filter by test case status (ENABLED, DISABLED, DRAFT, OUTDATED, DELETED, PROVISIONAL) + - runStatus: Filter by run status (ON, OFF) + - folderId: Filter by folder ID + - externalId: Filter by external ID + - AND: Logical AND operator for combining multiple conditions + - OR: Logical OR operator for combining multiple conditions + - NOT: Logical NOT operator for negating conditions + example: '{"status":"ENABLED","folderId":"some-folder-id","OR":[{"description":"Login Test"},{"externalId":"TEST-123"}]}' + responses: + '200': + description: List of test cases + content: + application/json: + schema: + $ref: '#/components/schemas/TestCasesResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}: + get: + summary: Retrieve a test case + description: Get detailed information about a specific test case + operationId: getTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + responses: + '200': + description: Test case details + content: + application/json: + schema: + $ref: '#/components/schemas/TestCaseResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target or test case not found + patch: + summary: Update a test case + description: Update specific properties of a test case + operationId: patchTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case to update + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + elements: + type: array + items: + $ref: '#/components/schemas/TestCaseElement' + description: + type: string + entryPointUrlPath: + type: string + nullable: true + status: + type: string + enum: + - ENABLED + - DISABLED + - DRAFT + - OUTDATED + - PROVISIONAL + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + folderName: + type: string + nullable: true + interactionStatus: + type: string + enum: + - NEW + - EDITED + - APPROVED + - REJECTED + createBackendDiscoveryPrompt: + type: string + assignedTagNames: + type: array + items: + type: string + externalId: + type: string + nullable: true + responses: + '200': + description: Updated test case + content: + application/json: + schema: + type: object + properties: + id: + type: string + format: uuid + testTargetId: + type: string + format: uuid + description: + type: string + status: + type: string + enum: + - ENABLED + - DISABLED + - DRAFT + - OUTDATED + - PROVISIONAL + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + entryPointUrlPath: + type: string + nullable: true + elements: + type: array + items: + type: object + folderId: + type: string + nullable: true + externalId: + type: string + nullable: true + tags: + type: array + items: + type: string + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case, folder or tag not found + delete: + summary: Delete a test case + description: Delete a specific test case + operationId: deleteTestCase + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + responses: + '200': + description: Test case deleted successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: + - deleted + description: Status of the deletion operation + required: + - status + '400': + description: Bad request + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ZodResponse' + - type: object + properties: + status: + type: string + enum: + - has dependencies + description: Indicates the test case has dependencies + dependencyIds: + type: array + items: + type: string + format: uuid + description: List of test case IDs that depend on this test case + required: + - status + - dependencyIds + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case not found + '500': + description: Internal server error + content: + application/json: + schema: + type: object + properties: + status: + type: string + enum: + - error + description: Indicates an error occurred + error: + type: string + description: Error message + required: + - status + - error + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/elements/{elementId}: + patch: + summary: Update a test case element + description: Update a the locator line of a specific test case element + operationId: updateTestCaseElement + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: elementId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case element + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + locatorLine: + description: The locator line of the test case element + type: string + required: + - locatorLine + responses: + '200': + description: Test case element updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestCaseElement' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target, test case or test case element not found + '500': + description: Internal server error + /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/code: + get: + summary: Retrieve code for a test case + description: Get the code representation of a specific test case + operationId: getTestCaseCode + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: executionUrl + in: query + required: true + schema: + type: string + format: url + description: URL of the app to test + - name: environmentId + in: query + required: false + schema: + type: string + format: uuid + description: Optional ID of the environment to use + responses: + '200': + description: Test case code retrieved successfully + content: + application/json: + schema: + type: object + properties: + testCode: + type: string + description: The code representation of the test case + required: + - testCode + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target, test case, or test code not found + /apiKey/beta/test-targets/{testTargetId}/pull: + get: + summary: Pull all test case from a test target to use it locally as json or yaml and work with the files directly. There is also a "opposite" endpoint to push the files back to the test target. + description: Get the pull data for a specific test target + operationId: getTestTargetPullData + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + responses: + '200': + description: Test target pull data retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/code: + post: + summary: Get TestSuite code + description: Retrieve the typescript code in a zip file for a set of yaml based test cases. This code can then be executed by playwright. + operationId: getTestTargetCode + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetCodeSchema' + responses: + '200': + description: Test target code retrieved successfully + content: + application/zip: + schema: + type: string + format: binary + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/push: + post: + summary: Push test target + description: Pushes all test cases with the specified schema to the test target. There is also a "opposite" endpoint to pull the files back to the local machine. + operationId: pushTestTarget + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + responses: + '200': + description: Test target pushed successfully + content: + application/json: + schema: + type: object + required: + - success + - versionIds + properties: + success: + type: boolean + versionIds: + type: array + items: + type: string + format: uuid + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/draft/push: + post: + summary: Push draft test target + description: Push all test cases from a local representation to a test target as a draft. + operationId: pushTestTargetDraft + security: + - ApiKeyAuth: [] + tags: + - Test targets + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestTargetSyncSchema' + responses: + '200': + description: Test target pushed successfully + content: + application/json: + schema: + type: object + required: + - success + - versionIds + - versionIdByStableId + properties: + success: + type: boolean + versionIds: + type: array + items: + type: string + format: uuid + description: List of test case IDs that were pushed to the test target + versionIdByStableId: + type: object + additionalProperties: + type: string + format: uuid + example: + 5afe4190-5900-47d0-8cfd-bbb7dce2460e: 48c9e7d0-67e3-4806-b255-3b9f79fa9639 + description: Version IDs per stable id that were pushed to the test target + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + /apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}: + get: + summary: Retrieve a specific test case version + description: Get detailed information about a specific version of a test case, including its local editing status. + operationId: getTestCaseVersion + security: + - ApiKeyAuth: [] + tags: + - Test Cases + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + - name: testCaseId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test case + - name: versionId + in: path + required: true + schema: + type: string + format: uuid + description: The version ID of the test case + responses: + '200': + description: Test case version details + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/items' + - type: object + required: + - versionId + - localEditingStatus + properties: + versionId: + type: string + format: uuid + description: The version ID of this test case + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + nullable: true + description: The local editing status of this test case version + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test case not found + /apiKey/v3/test-targets/{testTargetId}/discoveries: + post: + summary: Create a discovery + description: Create a new test case discovery with a given name and prompt + operationId: createDiscovery + security: + - ApiKeyAuth: [] + tags: + - Discoveries + parameters: + - name: testTargetId + in: path + required: true + schema: + type: string + format: uuid + description: The ID of the test target + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalDiscoveryBody' + responses: + '200': + description: Discovery created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/DiscoveryResponse' + '400': + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ZodResponse' + '401': + description: Unauthorized - Invalid or missing API key + '404': + description: Test target not found + '500': + description: Internal server error +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + schemas: + Breakpoint: + type: string + enum: + - MOBILE + - TABLET + - DESKTOP + description: The breakpoint to run the test cases against. + example: DESKTOP + default: DESKTOP + Browser: + type: string + enum: + - CHROMIUM + - FIREFOX + - SAFARI + description: The browser to run the test cases against. + example: CHROMIUM + default: CHROMIUM + TestTargetExecutionRequest: + type: object + properties: + testTargetId: + type: string + format: uuid + description: Unique identifier for the testTarget. + example: 2e2bb27b-a19c-47ce-a9b6-cd1bd31622dc + url: + type: string + format: uri + description: The URL of the test target for this run. + example: https://example.com + context: + $ref: '#/components/schemas/ExecutionContext' + environmentName: + type: string + format: environment name + description: the environment name you want to run your test against + default: default + variablesToOverwrite: + $ref: '#/components/schemas/Variables' + tags: + type: array + items: + type: string + example: + - tag1 + - tag2 + default: [] + description: The tags to filter the test cases by. + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + testCaseVersionIds: + type: array + items: + type: string + format: uuid + required: + - testTargetId + - url + - context + TestResult: + type: object + example: + id: 333d3f57-00cf-4ce9-8d34-89093a08681a + testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 + testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a + testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 + createdAt: '2024-09-06T13:01:51.686Z' + updatedAt: '2024-09-06T13:03:12.345Z' + status: PASSED + errorMessage: null + traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip + properties: + id: + type: string + format: uuid + description: Unique identifier for the test result. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: Unique identifier of the test report this result belongs to. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + testCaseVersionId: + type: string + format: uuid + description: Unique identifier of the test case version this result belongs to. + example: 5b844cf1-d597-4048-9e74-7c0f9ce3e2ee + createdAt: + type: string + format: date-time + description: The timestamp when the test result was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the test result was last updated. + example: '2024-09-06T13:01:51.686Z' + status: + type: string + enum: + - WAITING + - PASSED + - FAILED + - ERROR + description: The status of the specific test result, will be WAITING as long as the result is running, FAILED if the execution failed, PASSED if it succeeded, and ERROR if an internal error occurred. + errorMessage: + type: string + nullable: true + description: The error that has occurred during execution - only set if the status is FAILED or ERROR. + example: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' + traceUrl: + type: string + nullable: true + description: Link to the playwright trace of the test execution - only set once the test result is finished (PASSED or FAILED). + example: https://storage.googleapis.com/automagically-traces/826c15af-644b-4b28-89b4-f50ff34e46b7-trace.zip + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + TestReport: + type: object + required: + - id + - testTargetId + - createdAt + - updatedAt + - executionUrl + - status + - context + properties: + id: + type: string + format: uuid + description: Unique identifier for the test report. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: The unique identifier of the test target. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: + type: string + format: date-time + description: The timestamp when the test report was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the test report was last updated. + example: '2024-09-06T13:01:51.686Z' + executionUrl: + type: string + format: uri + description: The URL where the test execution was performed. + example: https://en.wikipedia.org/ + status: + type: string + enum: + - WAITING + - PASSED + - FAILED + description: The status of the test report, will be WAITING as long as any result is running, FAILED if the report is done but has any failed result and PASSED if all test results are done and successful + context: + $ref: '#/components/schemas/ExecutionContext' + breakpoint: + $ref: '#/components/schemas/Breakpoint' + browser: + $ref: '#/components/schemas/Browser' + testResults: + type: array + items: + $ref: '#/components/schemas/TestResult' + PrivateLocationInfo: + type: array + items: + type: object + properties: + status: + type: string + enum: + - OFFLINE + - ONLINE + address: + type: string + format: uri + example: https://example.com:3128 + name: + type: string + example: my-private-location + required: + - status + - address + - name + TestExecutionResponse: + type: object + required: + - testReportUrl + - testReport + properties: + testReportUrl: + type: string + format: uri + description: The URL where the test report can be accessed. + example: https://app.octomind.dev/testreports/826c15af-644b-4b28-89b4-f50ff34e46b7 + testReport: + $ref: '#/components/schemas/TestReport' + TestCasesResponse: + type: array + items: + $ref: '#/components/schemas/TestCaseResponse' + TestCaseResponse: + type: object + properties: + version: + type: string + enum: + - '1' + description: The version of the test case response. + id: + type: string + format: uuid + description: The ID of the test case. + testTargetId: + type: string + format: uuid + type: + type: string + nullable: true + elements: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + interaction: + type: object + nullable: true + properties: + id: + type: string + format: uuid + action: + type: string + enum: + - EXTRACT + - ENTER_TEXT + - CLICK + - SELECT_OPTION + - TYPE_TEXT + - KEY_PRESS + - HOVER + - UPLOAD + - GO_TO + - DRAG_AND_DROP + - CLOSE_PAGE + - OPEN_EMAIL + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + assertion: + type: object + nullable: true + properties: + id: + type: string + format: uuid + expectation: + type: string + enum: + - VISIBLE + - NOT_VISIBLE + - TO_BE_CHECKED + - NOT_TO_BE_CHECKED + - TO_HAVE_VALUE + - TO_CONTAIN_TEXT + - TO_HAVE_STYLE + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + scrollState: + type: object + nullable: true + selectors: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + selector: + type: string + selectorType: + type: string + enum: + - TEXT + - LABEL + - PLACEHOLDER + - ROLE + options: + type: object + nullable: true + properties: + name: + type: string + testCaseElementId: + type: string + format: uuid + scrollStateId: + type: string + nullable: true + testCaseId: + type: string + format: uuid + ignoreFailure: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + description: + type: string + status: + type: string + enum: + - ENABLED + - DRAFT + externalId: + type: string + nullable: true + entryPointUrlPath: + type: string + nullable: true + tags: + type: array + items: + type: string + createdBy: + type: string + enum: + - EDIT + runStatus: + type: string + enum: + - 'ON' + - 'OFF' + prerequisiteId: + type: string + nullable: true + proposalRunId: + type: string + nullable: true + folderId: + type: string + nullable: true + teardownTestCaseId: + type: string + nullable: true + discovery: + type: object + nullable: true + properties: + updatedAt: + type: string + format: date-time + freePrompt: + type: string + traceUrl: + type: string + nullable: true + status: + type: string + enum: + - CREATED + - IN_PROGRESS + - ERROR + - SUCCESS + - FAILED + - INCOMPLETE + - QUEUED + - STOPPED + - OUTDATED + - PAUSED + abortCause: + type: string + nullable: true + message: + type: string + nullable: true + required: + - id + - testTargetId + SuccessResponse: + type: object + properties: + success: + type: boolean + description: Indicates whether the operation was successful. + example: true + UnregisterRequest: + type: object + properties: + name: + type: string + RegisterRequest: + type: object + properties: + name: + type: string + registrationData: + type: object + properties: + proxypass: + type: string + example: secret22 + proxyuser: + type: string + example: user + address: + type: string + description: the address of the remote endpoint. IP and port + example: 34.45.23.22:23455 + EnvironmentsResponse: + type: array + items: + $ref: '#/components/schemas/EnvironmentResponse' + EnvironmentSimpleResponse: + type: object + properties: + id: + type: string + format: uuid + type: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + discoveryUrl: + type: string + format: url + example: https://example.com + email: + type: string + example: user@example.com + description: The 2FA email of the environment to test email flows. + EnvironmentResponse: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + testTargetId: + type: string + format: uuid + updatedAt: + type: string + format: date-time + type: + type: string + enum: + - DEFAULT + - ADDITIONAL + example: DEFAULT + discoveryUrl: + type: string + format: url + additionalHeaderFields: + type: object + additionalProperties: + type: string + nullable: true + testAccount: + type: object + properties: + username: + type: string + password: + type: string + otpInitializerKey: + type: string + nullable: true + updatedAt: + type: string + format: date-time + nullable: true + basicAuth: + type: object + properties: + username: + type: string + password: + type: string + updatedAt: + type: string + format: date-time + nullable: true + privateLocation: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + status: + type: string + type: + type: string + required: + - id + - name + - testTargetId + - type + CreateTestTargetBody: + type: object + properties: + testTarget: + type: object + properties: + app: + type: string + description: The app name or project name of the test target + discoveryUrl: + type: string + format: uri + description: The discovery URL of the test target. This is the URL that a discovery tool can use to discover the test target. + required: + - app + - discoveryUrl + TestTargetsResponse: + type: array + items: + $ref: '#/components/schemas/TestTargetResponse' + TestTargetResponse: + type: object + properties: + id: + type: string + format: uuid + app: + type: string + description: The app name or project name of the test target + tags: + type: array + items: + type: string + nullable: true + environments: + type: array + items: + $ref: '#/components/schemas/EnvironmentSimpleResponse' + description: The environments of the test target + required: + - id + - app + TestTargetUpdateRequest: + type: object + properties: + app: + type: string + nullable: true + description: The app name or project name of the test target + testIdAttribute: + type: string + nullable: true + description: The attribute name of the test ID + example: test-automation-id + testRailIntegration: + type: object + properties: + domain: + type: string + description: The domain of the TestRail instance + example: https://mycompany.testrail.io + username: + type: string + description: The username for the TestRail instance + example: user + projectId: + type: string + description: The project ID for the TestRail instance + example: '123' + apiKey: + type: string + description: The TestRail API key for the TestRail instance + example: '123123123' + nullable: true + timeoutPerStep: + type: number + format: int32 + minimum: 5000 + maximum: 30000 + description: The timeout per step in milliseconds + ZodResponse: + type: array + items: + type: object + properties: + code: + type: string + example: invalid_type + description: What error code happened while parsing the request + expected: + type: string + description: What the expected type was + example: object + received: + type: string + description: What the actual passed type was + example: string + path: + type: array + items: + type: string + description: The json path to the wrong parameter + example: key + message: + type: string + description: Human-readable message of the error that occurred while parsing. + example: Expected object, received string + ExternalBatchGenerationBody: + type: object + properties: + prompt: + type: string + description: Prompt to generate the test cases in this batch generation + imageUrls: + type: array + items: + type: string + description: Image URLs to generate the test cases in this batch generation + entryPointUrlPath: + type: string + nullable: true + description: Entry point URL path, where the batch generation will start + environmentId: + type: string + nullable: true + description: Environment ID, where the batch generation will be executed + prerequisiteId: + type: string + nullable: true + description: Prerequisite ID, which will be executed before the batch generation + baseUrl: + type: string + nullable: true + description: Base URL, where the batch generation will be executed + context: + $ref: '#/components/schemas/ExecutionContext' + BatchGenerationResponse: + type: object + properties: + batchGenerationId: + type: string + format: uuid + description: Unique identifier for the batch generation + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + Notification: + type: object + properties: + id: + type: string + format: uuid + description: Unique identifier for the event. + example: 826c15af-644b-4b28-89b4-f50ff34e46b7 + testTargetId: + type: string + format: uuid + description: The unique identifier of the test target this event belongs to. + example: 3435918b-3d29-4ebd-8c68-9a540532f45a + createdAt: + type: string + format: date-time + description: The timestamp when the event was created. + example: '2024-09-06T13:01:51.686Z' + updatedAt: + type: string + format: date-time + description: The timestamp when the event was last updated. + example: '2024-09-06T13:01:51.686Z' + payload: + type: object + description: JSON payload containing event-specific data. + example: + testCaseId: 5deeef76-f37a-418b-8101-7d69529fa561 + type: + type: string + enum: + - VALIDATION_PASSED + - VALIDATION_FAILED + - DISCOVERY_FINISHED + - PROPOSAL_SUCCESS + - PROPOSAL_FAILED + - REPORT_EXECUTION_FINISHED + description: The type of event that occurred. + example: VALIDATION_PASSED + ack: + type: string + enum: + - IN_WEB_APP + description: Optional acknowledgment status of the event. + example: IN_WEB_APP + nullable: true + required: + - id + - testTargetId + - createdAt + - updatedAt + - payload + - type + ExternalDiscoveryBody: + type: object + properties: + name: + type: string + description: Name of the discovered test case + example: Login Form Validation Test + entryPointUrlPath: + type: string + description: Entry point URL path of the discovered test case + example: /login + prerequisiteName: + type: string + description: Prerequisite test case name + example: Cookie Banner Acceptance + externalId: + type: string + description: External ID of the discovered test case + example: ext-test-001 + tagNames: + type: array + items: + type: string + description: Tags to assign to the discovered test case + example: + - authentication + - forms + - validation + prompt: + type: string + description: Prompt to generate the discovered test case + example: Test the login form with valid and invalid credentials + folderName: + type: string + description: Folder name of the discovered test case + example: Authentication Tests + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + description: Type of the discovered test case + example: LOGIN + required: + - name + - prompt + DiscoveryResponse: + type: object + required: + - discoveryId + - testCaseId + properties: + discoveryId: + type: string + format: uuid + description: The ID of the created discovery + testCaseId: + type: string + format: uuid + description: The ID of the associated test case + ExecutionContext: + oneOf: + - type: object + properties: + source: + type: string + enum: + - github + example: github + issueNumber: + type: integer + nullable: true + example: 123 + ref: + type: string + nullable: true + example: refs/heads/main + sha: + type: string + nullable: true + example: abc123def456 + repo: + type: string + example: my-repo + owner: + type: string + example: repo-owner + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + nodeId: + type: string + nullable: true + example: node-123 + - type: object + properties: + source: + type: string + enum: + - azureDevOps + example: azureDevOps + accessToken: + type: string + example: token123 + organization: + type: string + example: my-org + project: + type: string + example: my-project + repositoryId: + type: string + example: repo-123 + sha: + type: string + nullable: true + example: abc123def456 + ref: + type: string + nullable: true + example: refs/heads/main + pullRequestId: + type: integer + nullable: true + example: 101 + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + threadId: + type: string + nullable: true + example: thread-123 + - type: object + properties: + source: + type: string + enum: + - discovery + example: discovery + description: + type: string + example: A discovery test + triggeredBy: + oneOf: + - type: object + properties: + type: + type: string + enum: + - INITIAL + example: INITIAL + - type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - manual + example: manual + description: + type: string + example: A manual trigger + triggeredBy: + type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - scheduled + example: scheduled + triggeredBy: + type: object + nullable: true + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + - type: object + properties: + source: + type: string + enum: + - proposal + example: proposal + description: + type: string + example: A proposal trigger + triggeredBy: + oneOf: + - type: object + properties: + type: + type: string + enum: + - INITIAL + example: INITIAL + - type: object + properties: + type: + type: string + enum: + - USER + example: USER + userId: + type: string + example: user123 + Variables: + type: object + additionalProperties: + type: array + items: + type: string + description: The variables to overwrite exclusively for this test run. + example: + SPACE_ID: + - 64ee32b4-7365-47a6-b5b0-2903b6ad849d + TestCaseElement: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + interaction: + type: object + nullable: true + properties: + id: + type: string + format: uuid + action: + type: string + enum: + - EXTRACT + - ENTER_TEXT + - CLICK + - SELECT_OPTION + - TYPE_TEXT + - KEY_PRESS + - HOVER + - UPLOAD + - GO_TO + - DRAG_AND_DROP + - CLOSE_PAGE + - OPEN_EMAIL + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + assertion: + type: object + nullable: true + properties: + id: + type: string + format: uuid + expectation: + type: string + enum: + - VISIBLE + - NOT_VISIBLE + - TO_BE_CHECKED + - NOT_TO_BE_CHECKED + - TO_HAVE_VALUE + - TO_CONTAIN_TEXT + - TO_HAVE_STYLE + calledWith: + type: string + nullable: true + testCaseElementId: + type: string + format: uuid + selectors: + type: array + items: + type: object + properties: + id: + type: string + format: uuid + index: + type: integer + selector: + type: string + selectorType: + type: string + enum: + - TEXT + - LABEL + - PLACEHOLDER + - ROLE + options: + type: object + nullable: true + testCaseElementId: + type: string + format: uuid + testCaseId: + type: string + format: uuid + ignoreFailure: + type: boolean + TestTargetSyncSchema: + title: TestTargetSyncSchema + description: schema for import and export of test cases + type: object + properties: + testCases: + type: array + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false + required: + - testCases + additionalProperties: false + TestTargetCodeSchema: + title: TestTargetCodeSchema + description: schema for export of test cases as code + type: object + properties: + executionUrl: + type: string + format: uri + environmentId: + type: string + format: uuid + testCases: + type: array + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false + required: + - executionUrl + - testCases + additionalProperties: false + items: + type: object + properties: + version: + type: string + enum: + - '1' + id: + type: string + format: uuid + type: + type: string + enum: + - LOGIN + - COOKIE_BANNER + - LINK + - TEARDOWN + tagNames: + type: array + items: + type: string + excludedEnvironmentNames: + type: array + items: + type: string + runStatus: + type: string + enum: + - 'OFF' + - 'ON' + dependencyId: + type: string + format: uuid + teardownId: + type: string + format: uuid + elements: + type: array + items: + type: object + properties: + interaction: + anyOf: + - type: object + properties: + action: + type: string + enum: + - CLICK + calledWith: + type: object + properties: + button: + type: string + enum: + - right + - left + - middle + double: + type: boolean + force: + type: boolean + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - ENTER_TEXT + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SELECT_OPTION + calledWith: + anyOf: + - anyOf: + - anyOf: + - type: string + - type: array + items: + type: string + - type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + - type: array + items: + type: object + properties: + value: + type: string + label: + type: string + index: + type: number + additionalProperties: false + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - GO_TO + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - KEY_PRESS + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - HOVER + calledWith: + not: {} + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - UPLOAD + calledWith: + type: string + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DOWNLOAD + calledWith: + anyOf: + - type: string + - {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - DRAG_AND_DROP + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - TYPE_TEXT + calledWith: + type: object + properties: + text: + type: string + delay: + type: number + required: + - text + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - EXTRACT + calledWith: + anyOf: + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - TEXT + required: + - variableName + - type + additionalProperties: false + - type: object + properties: + variableName: + type: string + type: + type: string + enum: + - ATTRIBUTE + attributeName: + type: string + required: + - variableName + - type + - attributeName + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - JAVASCRIPT + calledWith: + type: object + properties: + code: + type: string + execType: + type: string + enum: + - browser + - sandbox + default: sandbox + required: + - code + additionalProperties: false + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - OPEN_EMAIL + calledWith: + type: object + properties: + subjectContaining: + type: string + nullable: true + subAddress: + type: string + pattern: ^[^ @]*$ + nullable: true + required: + - subjectContaining + - subAddress + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - CLOSE_PAGE + calledWith: + enum: + - 'null' + nullable: true + required: + - action + - calledWith + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - WAIT_FOR + calledWith: + anyOf: + - type: object + properties: + type: + type: string + enum: + - load + - domContentLoaded + - networkIdle + - nuxtHydration + - nuxtDelayHydration + - webComponentsHydration + - preactHydration + - qwikHydration + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - fixedTime + timeMilliseconds: + type: number + required: + - type + additionalProperties: false + - type: object + properties: + type: + type: string + enum: + - code + code: + type: string + required: + - type + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + - type: object + properties: + action: + type: string + enum: + - SCROLL + calledWith: + type: object + properties: + x: + type: number + 'y': + type: number + additionalProperties: false + nullable: true + required: + - action + additionalProperties: false + assertion: + anyOf: + - type: object + properties: + expectation: + type: string + enum: + - VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_VISIBLE + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - RESPONSE_OK + calledWith: + type: string + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - DISABLED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_VALUE + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - NOT_TO_BE_CHECKED + calledWith: + not: {} + nullable: true + required: + - expectation + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_CONTAIN_TEXT + calledWith: + anyOf: + - type: string + - {} + required: + - expectation + - calledWith + additionalProperties: false + - type: object + properties: + expectation: + type: string + enum: + - TO_HAVE_STYLE + calledWith: + type: array + minItems: 2 + maxItems: 2 + prefixItems: + - type: string + - anyOf: + - type: string + - {} + items: {} + required: + - expectation + - calledWith + additionalProperties: false + ignoreFailure: + type: boolean + selectors: + type: array + items: + type: object + properties: + selectorType: + type: string + enum: + - FRAME + - CSS + - TEXT + - ROLE + - ALT_TEXT + - LABEL + - TEST_ID + - TITLE + - PLACEHOLDER + - FILTER + - FIRST + - LAST + - NTH + selector: + anyOf: + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + enum: + - alert + - alertdialog + - application + - article + - banner + - blockquote + - button + - caption + - cell + - checkbox + - code + - columnheader + - combobox + - complementary + - contentinfo + - definition + - deletion + - dialog + - directory + - document + - emphasis + - feed + - figure + - form + - generic + - grid + - gridcell + - group + - heading + - img + - insertion + - link + - list + - listbox + - listitem + - log + - main + - marquee + - math + - meter + - menu + - menubar + - menuitem + - menuitemcheckbox + - menuitemradio + - navigation + - none + - note + - option + - paragraph + - presentation + - progressbar + - radio + - radiogroup + - region + - row + - rowgroup + - rowheader + - scrollbar + - search + - searchbox + - separator + - slider + - spinbutton + - status + - strong + - subscript + - superscript + - switch + - tab + - table + - tablist + - tabpanel + - term + - textbox + - time + - timer + - toolbar + - tooltip + - tree + - treegrid + - treeitem + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - anyOf: + - type: string + - {} + - type: string + - type: string + - type: string + nullable: true + options: + type: object + properties: + name: + anyOf: + - type: string + - {} + exact: + type: boolean + checked: + type: boolean + disabled: + type: boolean + expanded: + type: boolean + includeHidden: + type: boolean + level: + type: number + pressed: + type: boolean + selected: + type: boolean + hasText: + anyOf: + - type: string + - {} + hasNotText: + anyOf: + - type: string + - {} + additionalProperties: false + nullable: true + required: + - selectorType + additionalProperties: false + required: + - selectors + additionalProperties: false + description: + type: string + entryPointUrlPath: + type: string + folderName: + type: string + externalId: + type: string + prompt: + type: string + localEditingStatus: + type: string + enum: + - IN_PROGRESS + - DONE + - CANCELLED + required: + - version + - id + - runStatus + - elements + - description + - prompt + additionalProperties: false diff --git a/package.json b/package.json index c18568a..44a26a8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dev:local": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.local.json OCTOMIND_API_URL=http://localhost:3000/api tsx src/index.ts", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.prod.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", - "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", + "apigen": "openapi-typescript openapi.yaml --output src/api.ts && orval", "build": "pnpm apigen && npx genversion -des src/version.ts && pnpm gendoc && tsc --project tsconfig.build.json", "octomind": "pnpm apigen && tsx src/index.ts", "test": "pnpm apigen && npx genversion -des src/version.ts && vitest", @@ -41,6 +41,7 @@ "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", + "diff": "8.0.3", "open": "11.0.0", "openapi-fetch": "0.15.0", "otplib": "13.1.0", diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 7963cbf..5c90c61 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,6 +1,6 @@ -import fs, { writeFileSync } from "fs"; -import path from "path"; +import fs from "fs"; +import { createTwoFilesPatch } from "diff"; import open from "open"; import yaml from "yaml"; @@ -72,6 +72,7 @@ const getTestCaseToEdit = async ( }, ); }; + export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -80,12 +81,10 @@ export const edit = async (options: EditOptions): Promise => { ); } - console.log({ octomindRoot }); const testCaseFilePath = await getAbsoluteFilePathInOctomindRoot({ octomindRoot, filePath: options.filePath, }); - console.log({ testCaseFilePath }); if (!testCaseFilePath) { throw new Error( @@ -93,9 +92,10 @@ export const edit = async (options: EditOptions): Promise => { ); } + const originalTestCase = loadTestCase(testCaseFilePath); const testCaseToEdit = { - ...loadTestCase(testCaseFilePath), - localEditingStatus: "IN_PROGRESS", + ...originalTestCase, + localEditingStatus: "IN_PROGRESS" as const, }; const testCases = readTestCasesFromDir(octomindRoot); @@ -119,12 +119,10 @@ export const edit = async (options: EditOptions): Promise => { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const versionId = response?.versionIdByStableId[testCaseToEdit.id]; - + const versionId = response.versionIdByStableId[testCaseToEdit.id]; if (!versionId) { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const parsedBaseUrl = URL.parse(BASE_URL); const localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; await open(localEditingUrl); @@ -139,19 +137,44 @@ export const edit = async (options: EditOptions): Promise => { options, ); - while (localTestCase.data?.localEditingStatus === "IN_PROGRESS") { + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + + while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { await sleep(POLLING_INTERVAL); console.log("Waiting for local editing to finish..."); localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } } - await writeSingleTestCaseYaml(testCaseFilePath, { + const syncTestCaseWithoutExtraProperties: SyncTestCase & { + versionId: undefined; + } = { ...localTestCase.data, id: testCaseToEdit.id, localEditingStatus: undefined, versionId: undefined, - }); + }; - console.log("Edited test case successfully!"); + await writeSingleTestCaseYaml( + testCaseFilePath, + syncTestCaseWithoutExtraProperties, + ); + + const diff = createTwoFilesPatch( + "old.yaml", + "new.yaml", + yaml.stringify(originalTestCase), + yaml.stringify(syncTestCaseWithoutExtraProperties), + ); + console.log(`Edited test case successfully`); + console.log(diff); }; From 51c1b9c46c1e07b6dbde454f34d3345fdb93f247 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:03:35 +0100 Subject: [PATCH 27/36] throbber! --- package.json | 9 +++- src/tools/yamlMutations/edit.ts | 94 ++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 44a26a8..24bece3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,13 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", - "files": ["dist", "src", "README.md", "LICENSE", "package.json"], + "files": [ + "dist", + "src", + "README.md", + "LICENSE", + "package.json" + ], "engines": { "node": ">=22.0.0" }, @@ -44,6 +50,7 @@ "diff": "8.0.3", "open": "11.0.0", "openapi-fetch": "0.15.0", + "ora": "9.0.0", "otplib": "13.1.0", "shell-quote": "1.8.3", "simple-git": "3.30.0", diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 5c90c61..b2b1d89 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,7 +1,8 @@ import fs from "fs"; -import { createTwoFilesPatch } from "diff"; +import { createTwoFilesPatch, parsePatch } from "diff"; import open from "open"; +import ora from "ora"; import yaml from "yaml"; import { OCTOMIND_FOLDER_NAME } from "../../constants"; @@ -54,9 +55,9 @@ const loadTestCase = (testCasePath: string): SyncTestCase => { }; const POLLING_INTERVAL = 1000; -const getTestCaseToEdit = async ( +const getTestCaseVersion = async ( versionId: string, - testCaseToEdit: SyncTestCase, + testCase: SyncTestCase, options: EditOptions, ) => { return await client.GET( @@ -65,7 +66,7 @@ const getTestCaseToEdit = async ( params: { path: { versionId, - testCaseId: testCaseToEdit.id, + testCaseId: testCase.id, testTargetId: options.testTargetId, }, }, @@ -73,6 +74,53 @@ const getTestCaseToEdit = async ( ); }; +const waitForLocalEditingToBeFinished = async ( + versionId: string, + testCaseToEdit: SyncTestCase, + options: EditOptions, +): Promise => { + let localTestCase = await getTestCaseVersion( + versionId, + testCaseToEdit, + options, + ); + + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + + const throbber = ora("Waiting for local editing to finish..").start(); + while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { + await sleep(POLLING_INTERVAL); + + localTestCase = await getTestCaseVersion( + versionId, + testCaseToEdit, + options, + ); + if (!localTestCase.data) { + throw new Error( + `Could not get local editing status for test case ${testCaseToEdit.id}`, + ); + } + } + + throbber.succeed(); + + const syncTestCaseWithoutExtraProperties: SyncTestCase & { + versionId: undefined; + } = { + ...localTestCase.data, + id: testCaseToEdit.id, + localEditingStatus: undefined, + versionId: undefined, + }; + + return syncTestCaseWithoutExtraProperties; +}; + export const edit = async (options: EditOptions): Promise => { const octomindRoot = await findOctomindFolder(); if (!octomindRoot) { @@ -101,7 +149,6 @@ export const edit = async (options: EditOptions): Promise => { const testCases = readTestCasesFromDir(octomindRoot); const testCasesById = Object.fromEntries(testCases.map((tc) => [tc.id, tc])); const relevantTestCases = getRelevantTestCases(testCasesById, testCaseToEdit); - checkForConsistency(relevantTestCases); const response = await draftPush( @@ -131,50 +178,21 @@ export const edit = async (options: EditOptions): Promise => { `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); - let localTestCase = await getTestCaseToEdit( + const editResult = await waitForLocalEditingToBeFinished( versionId, testCaseToEdit, options, ); - if (!localTestCase.data) { - throw new Error( - `Could not get local editing status for test case ${testCaseToEdit.id}`, - ); - } - - while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { - await sleep(POLLING_INTERVAL); - console.log("Waiting for local editing to finish..."); - - localTestCase = await getTestCaseToEdit(versionId, testCaseToEdit, options); - if (!localTestCase.data) { - throw new Error( - `Could not get local editing status for test case ${testCaseToEdit.id}`, - ); - } - } - - const syncTestCaseWithoutExtraProperties: SyncTestCase & { - versionId: undefined; - } = { - ...localTestCase.data, - id: testCaseToEdit.id, - localEditingStatus: undefined, - versionId: undefined, - }; - - await writeSingleTestCaseYaml( - testCaseFilePath, - syncTestCaseWithoutExtraProperties, - ); + await writeSingleTestCaseYaml(testCaseFilePath, editResult); const diff = createTwoFilesPatch( "old.yaml", "new.yaml", yaml.stringify(originalTestCase), - yaml.stringify(syncTestCaseWithoutExtraProperties), + yaml.stringify(editResult), ); + console.log(`Edited test case successfully`); console.log(diff); }; From b3e59985ff32c8cc06b42c4c62f210edfe7ed01a Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 13 Jan 2026 19:52:04 +0100 Subject: [PATCH 28/36] add cancellation, better throbber exiting --- src/tools/yamlMutations/edit.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b2b1d89..8525844 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -78,7 +78,7 @@ const waitForLocalEditingToBeFinished = async ( versionId: string, testCaseToEdit: SyncTestCase, options: EditOptions, -): Promise => { +): Promise => { let localTestCase = await getTestCaseVersion( versionId, testCaseToEdit, @@ -91,7 +91,7 @@ const waitForLocalEditingToBeFinished = async ( ); } - const throbber = ora("Waiting for local editing to finish..").start(); + const throbber = ora("Waiting for editing to finish in UI").start(); while (localTestCase.data.localEditingStatus === "IN_PROGRESS") { await sleep(POLLING_INTERVAL); @@ -105,9 +105,14 @@ const waitForLocalEditingToBeFinished = async ( `Could not get local editing status for test case ${testCaseToEdit.id}`, ); } + + if (localTestCase.data.localEditingStatus === "CANCELLED") { + throbber.fail("cancelled by user"); + return "cancelled"; + } } - throbber.succeed(); + throbber.succeed("Finished editing in UI"); const syncTestCaseWithoutExtraProperties: SyncTestCase & { versionId: undefined; @@ -184,6 +189,11 @@ export const edit = async (options: EditOptions): Promise => { options, ); + if (editResult === "cancelled") { + console.log("Cancelled editing test case, exiting"); + return; + } + await writeSingleTestCaseYaml(testCaseFilePath, editResult); const diff = createTwoFilesPatch( From 28ce0026f8de894c0030552b9c0e8c2d85831cf7 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 10:38:52 +0100 Subject: [PATCH 29/36] remove import --- src/tools/yamlMutations/edit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 8525844..042dae5 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -1,6 +1,6 @@ import fs from "fs"; -import { createTwoFilesPatch, parsePatch } from "diff"; +import { createTwoFilesPatch } from "diff"; import open from "open"; import ora from "ora"; import yaml from "yaml"; From d8814ab823fd61d28e3e18ca6b9f44b827ecfcf1 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:03:56 +0100 Subject: [PATCH 30/36] -debugging stuff --- openapi.yaml | 4735 -------------------------------------------------- package.json | 10 +- 2 files changed, 2 insertions(+), 4743 deletions(-) delete mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml deleted file mode 100644 index e3774b0..0000000 --- a/openapi.yaml +++ /dev/null @@ -1,4735 +0,0 @@ -openapi: 3.1.0 -info: - title: Octomind external API - description: Octomind API that allows you to execute test cases, fetch reports and register private location workers by providing a URL and an ID. - version: 1.0.0 - contact: - name: API Support - url: https://octomind.dev/docs - email: support@octomind.dev -servers: - - url: https://app.octomind.dev/api - description: Main API Endpoint -tags: - - name: Test targets - description: Operations for managing test targets - - name: Execute - description: Operations for executing tests - - name: Environments - description: Operations for managing test environments - - name: Reports - description: Operations for retrieving test reports - - name: Notifications - description: Operations for retrieving notifications - - name: Private locations - description: Operations for managing private locations - - name: Test Cases - description: Operations for retrieving test cases - - name: Discoveries - description: Operations for creating test discoveries -externalDocs: - description: Find out more - url: https://octomind.dev/docs/ -paths: - /apiKey/v3/test-targets: - get: - summary: Retrieve all test targets - description: Gets a list of test targets. - operationId: getTestTargets - tags: - - Test targets - security: - - ApiKeyAuth: [] - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetsResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - post: - summary: Create a new test target - description: Creates a new test target. - operationId: createTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTestTargetBody' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}: - get: - summary: Retrieve a test target - description: Gets a test target by ID. - operationId: getTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to fetch - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - patch: - summary: Update a test target - description: Updates a test target by ID. - operationId: updateTestTarget - tags: - - Test targets - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to update - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetUpdateRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - delete: - summary: Delete a test target - description: Deletes a test target by ID. - operationId: deleteTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to delete - responses: - '204': - description: OK - '401': - description: Invalid or missing API key - '404': - description: Test target not found - '500': - description: Internal server error - /apiKey/v3/execute: - post: - summary: Execute tests of the given test target - description: | - This endpoint triggers a test execution by sending an test target id, an URL and optionally tags, an environment and variables. - operationId: executeTests - tags: - - Execute - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetExecutionRequest' - responses: - '200': - description: Test executed successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestExecutionResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/batch-generations: - post: - summary: Create a batch generation - description: Creates a batch generation for the given test target. - operationId: createBatchGeneration - tags: - - Discoveries - security: - - ApiKeyAuth: [] - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalBatchGenerationBody' - responses: - '200': - description: Batch generation created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BatchGenerationResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/config: - get: - summary: Retrieve test target configuration - description: Get the test target configuration for a specific environment - operationId: getTestTargetConfig - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: url - in: query - required: true - schema: - type: string - format: uri - description: The execution URL for the test target - - name: outputDir - in: query - required: true - schema: - type: string - description: The directory where test output will be stored - - name: headless - in: query - required: false - schema: - type: string - description: Whether to run tests in headless mode (true/false) - - name: environmentId - in: query - required: false - schema: - type: string - format: uuid - description: Optional ID of the environment to use (defaults to the test target's default environment if not provided) - responses: - '200': - description: Test target configuration retrieved successfully - content: - text/plain: - schema: - type: string - description: The test target configuration as plain text - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Environment not found - '405': - description: Method not allowed - /apiKey/v3/test-targets/{testTargetId}/environments: - post: - summary: Create an environment - description: Create a custom environment. - operationId: createEnvironment - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the environment belongs to - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - discoveryUrl: - type: string - format: url - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - nullable: true - privateLocationName: - type: string - description: name of the private location - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - responses: - '201': - description: environment created - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - get: - summary: Retrieve environments - description: get a list of all defined environments. - operationId: getEnvironments - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - responses: - '200': - description: environments - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentsResponse' - /apiKey/v3/test-targets/{testTargetId}/environments/{environmentId}: - patch: - summary: Update an environment - description: Updates an enviroment, all properties can be set separately - operationId: updateEnvironment - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the environment belongs to - - in: path - name: environmentId - required: true - schema: - type: string - format: uuid - description: ID of the environment to update - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - nullable: true - discoveryUrl: - type: string - format: url - nullable: true - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - nullable: true - privateLocationName: - type: string - nullable: true - description: name of the private location - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - responses: - '200': - description: Environment updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentResponse' - delete: - summary: Delete an environment - operationId: deleteEnvironment - description: deletes an enviroment. this operation is not reversable. - security: - - ApiKeyAuth: [] - tags: - - Environments - responses: - '200': - description: Environment deleted successfully - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - - in: path - name: environmentId - required: true - schema: - type: string - format: uuid - description: ID of the environment to update - /apiKey/v3/test-targets/{testTargetId}/test-reports/{testReportId}: - get: - summary: Retrieve information about a test report - description: Poll from within a CI-pipeline to wait for the completion of a report. - operationId: getTestReport - tags: - - Reports - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - - in: path - name: testReportId - required: true - schema: - type: string - format: uuid - description: ID of the test report to fetch - security: - - ApiKeyAuth: [] - responses: - '200': - description: Test Report information - content: - application/json: - schema: - $ref: '#/components/schemas/TestReport' - example: - id: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:05:23.412Z' - executionUrl: https://en.wikipedia.org/ - status: FAILED - context: - source: github - issueNumber: 123 - ref: refs/heads/main - sha: abc123def456 - repo: my-repo - owner: repo-owner - triggeredBy: - type: USER - userId: 3435918b-3d29-4ebd-8c68-9a540532f45a - nodeId: node-123 - testResults: - - id: 9876fedc-ba98-7654-3210-fedcba987654 - testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:03:12.345Z' - status: PASSED - errorMessage: null - traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip - - id: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: 5432fedc-ba98-7654-3210-fedcba543210 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:04:45.678Z' - status: FAILED - errorMessage: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' - traceUrl: https://storage.googleapis.com/automagically-traces/2a3b4c5d-6e7f-8g9h-0i1j-2k3l4m5n6o7p-trace.zip - - id: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: 1234abcd-ef56-7890-abcd-ef1234567890 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:05:23.412Z' - status: WAITING - errorMessage: null - traceUrl: null - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-reports: - get: - summary: Retrieve paginated information about test reports - description: Allow fetching the history of test reports for your test target. - operationId: getTestReports - tags: - - Reports - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target for which to fetch the history for - - in: query - name: key - required: false - schema: - type: object - properties: - createdAt: - type: string - format: date-time - description: The timestamp of the key of the next page to fetch - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) - example: '2024-09-06T13:01:51.686Z' - - in: query - name: filter - required: false - schema: - type: array - items: - type: object - properties: - key: - type: string - description: The name of the property to filter for, e.g. an environmentId - example: environmentId - operator: - type: string - enum: - - EQUALS - description: How to compare the property in question, only EQUALS is supported so far. - value: - type: string - format: uuid - description: The value to compare with to find matches. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - security: - - ApiKeyAuth: [] - responses: - '200': - description: Test Reports information - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/TestReport' - key: - type: object - properties: - createdAt: - type: string - format: date-time - description: The timestamp of the key of the next page to fetch - use this in the query when fetching the next page of reports - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) - example: '2024-09-06T13:01:51.686Z' - hasNextPage: - type: boolean - description: If the query in question has another page to retrieve - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/notifications: - get: - summary: Retrieve notifications - description: Get a list of notifications for a specific test target. - operationId: getNotifications - security: - - ApiKeyAuth: [] - tags: - - Notifications - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - responses: - '200': - description: List of notifications - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Notification' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/v1/private-location: - get: - security: - - ApiKeyAuth: [] - summary: Retrieve all private locations - description: gets a list of private location workers - operationId: getPrivateLocations - tags: - - Private locations - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PrivateLocationInfo' - /apiKey/v1/private-location/register: - put: - security: - - ApiKeyAuth: [] - summary: Register a private location - description: registers a private location worker - operationId: registerPrivateLocation - tags: - - Private locations - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RegisterRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '404': - description: private location of that name not found - '500': - description: Internal server error - /apiKey/v1/private-location/unregister: - put: - security: - - ApiKeyAuth: [] - summary: Unregister a private location - description: Unregisters a private location worker. - operationId: unregisterPrivateLocation - tags: - - Private locations - externalDocs: - url: https://octomind.dev/docs/proxy/private-location - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UnregisterRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '404': - description: private location of that name not found - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-cases: - get: - summary: List test cases - description: Get a list of test cases for a specific test target with optional filtering - operationId: getTestCases - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: filter - in: query - required: false - schema: - type: string - description: | - JSON string containing filter criteria for test cases. The filter supports the following fields: - - testTargetId: Filter by test target ID - - description: Filter by test case description - - status: Filter by test case status (ENABLED, DISABLED, DRAFT, OUTDATED, DELETED, PROVISIONAL) - - runStatus: Filter by run status (ON, OFF) - - folderId: Filter by folder ID - - externalId: Filter by external ID - - AND: Logical AND operator for combining multiple conditions - - OR: Logical OR operator for combining multiple conditions - - NOT: Logical NOT operator for negating conditions - example: '{"status":"ENABLED","folderId":"some-folder-id","OR":[{"description":"Login Test"},{"externalId":"TEST-123"}]}' - responses: - '200': - description: List of test cases - content: - application/json: - schema: - $ref: '#/components/schemas/TestCasesResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}: - get: - summary: Retrieve a test case - description: Get detailed information about a specific test case - operationId: getTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - responses: - '200': - description: Test case details - content: - application/json: - schema: - $ref: '#/components/schemas/TestCaseResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target or test case not found - patch: - summary: Update a test case - description: Update specific properties of a test case - operationId: patchTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case to update - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - elements: - type: array - items: - $ref: '#/components/schemas/TestCaseElement' - description: - type: string - entryPointUrlPath: - type: string - nullable: true - status: - type: string - enum: - - ENABLED - - DISABLED - - DRAFT - - OUTDATED - - PROVISIONAL - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - folderName: - type: string - nullable: true - interactionStatus: - type: string - enum: - - NEW - - EDITED - - APPROVED - - REJECTED - createBackendDiscoveryPrompt: - type: string - assignedTagNames: - type: array - items: - type: string - externalId: - type: string - nullable: true - responses: - '200': - description: Updated test case - content: - application/json: - schema: - type: object - properties: - id: - type: string - format: uuid - testTargetId: - type: string - format: uuid - description: - type: string - status: - type: string - enum: - - ENABLED - - DISABLED - - DRAFT - - OUTDATED - - PROVISIONAL - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - entryPointUrlPath: - type: string - nullable: true - elements: - type: array - items: - type: object - folderId: - type: string - nullable: true - externalId: - type: string - nullable: true - tags: - type: array - items: - type: string - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case, folder or tag not found - delete: - summary: Delete a test case - description: Delete a specific test case - operationId: deleteTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - responses: - '200': - description: Test case deleted successfully - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: - - deleted - description: Status of the deletion operation - required: - - status - '400': - description: Bad request - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/ZodResponse' - - type: object - properties: - status: - type: string - enum: - - has dependencies - description: Indicates the test case has dependencies - dependencyIds: - type: array - items: - type: string - format: uuid - description: List of test case IDs that depend on this test case - required: - - status - - dependencyIds - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case not found - '500': - description: Internal server error - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: - - error - description: Indicates an error occurred - error: - type: string - description: Error message - required: - - status - - error - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/elements/{elementId}: - patch: - summary: Update a test case element - description: Update a the locator line of a specific test case element - operationId: updateTestCaseElement - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: elementId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case element - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - locatorLine: - description: The locator line of the test case element - type: string - required: - - locatorLine - responses: - '200': - description: Test case element updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestCaseElement' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target, test case or test case element not found - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/code: - get: - summary: Retrieve code for a test case - description: Get the code representation of a specific test case - operationId: getTestCaseCode - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: executionUrl - in: query - required: true - schema: - type: string - format: url - description: URL of the app to test - - name: environmentId - in: query - required: false - schema: - type: string - format: uuid - description: Optional ID of the environment to use - responses: - '200': - description: Test case code retrieved successfully - content: - application/json: - schema: - type: object - properties: - testCode: - type: string - description: The code representation of the test case - required: - - testCode - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target, test case, or test code not found - /apiKey/beta/test-targets/{testTargetId}/pull: - get: - summary: Pull all test case from a test target to use it locally as json or yaml and work with the files directly. There is also a "opposite" endpoint to push the files back to the test target. - description: Get the pull data for a specific test target - operationId: getTestTargetPullData - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - responses: - '200': - description: Test target pull data retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/code: - post: - summary: Get TestSuite code - description: Retrieve the typescript code in a zip file for a set of yaml based test cases. This code can then be executed by playwright. - operationId: getTestTargetCode - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetCodeSchema' - responses: - '200': - description: Test target code retrieved successfully - content: - application/zip: - schema: - type: string - format: binary - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/push: - post: - summary: Push test target - description: Pushes all test cases with the specified schema to the test target. There is also a "opposite" endpoint to pull the files back to the local machine. - operationId: pushTestTarget - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - responses: - '200': - description: Test target pushed successfully - content: - application/json: - schema: - type: object - required: - - success - - versionIds - properties: - success: - type: boolean - versionIds: - type: array - items: - type: string - format: uuid - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/draft/push: - post: - summary: Push draft test target - description: Push all test cases from a local representation to a test target as a draft. - operationId: pushTestTargetDraft - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - responses: - '200': - description: Test target pushed successfully - content: - application/json: - schema: - type: object - required: - - success - - versionIds - - versionIdByStableId - properties: - success: - type: boolean - versionIds: - type: array - items: - type: string - format: uuid - description: List of test case IDs that were pushed to the test target - versionIdByStableId: - type: object - additionalProperties: - type: string - format: uuid - example: - 5afe4190-5900-47d0-8cfd-bbb7dce2460e: 48c9e7d0-67e3-4806-b255-3b9f79fa9639 - description: Version IDs per stable id that were pushed to the test target - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}: - get: - summary: Retrieve a specific test case version - description: Get detailed information about a specific version of a test case, including its local editing status. - operationId: getTestCaseVersion - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: versionId - in: path - required: true - schema: - type: string - format: uuid - description: The version ID of the test case - responses: - '200': - description: Test case version details - content: - application/json: - schema: - allOf: - - $ref: '#/components/schemas/items' - - type: object - required: - - versionId - - localEditingStatus - properties: - versionId: - type: string - format: uuid - description: The version ID of this test case - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - nullable: true - description: The local editing status of this test case version - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case not found - /apiKey/v3/test-targets/{testTargetId}/discoveries: - post: - summary: Create a discovery - description: Create a new test case discovery with a given name and prompt - operationId: createDiscovery - security: - - ApiKeyAuth: [] - tags: - - Discoveries - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalDiscoveryBody' - responses: - '200': - description: Discovery created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/DiscoveryResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - '500': - description: Internal server error -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - schemas: - Breakpoint: - type: string - enum: - - MOBILE - - TABLET - - DESKTOP - description: The breakpoint to run the test cases against. - example: DESKTOP - default: DESKTOP - Browser: - type: string - enum: - - CHROMIUM - - FIREFOX - - SAFARI - description: The browser to run the test cases against. - example: CHROMIUM - default: CHROMIUM - TestTargetExecutionRequest: - type: object - properties: - testTargetId: - type: string - format: uuid - description: Unique identifier for the testTarget. - example: 2e2bb27b-a19c-47ce-a9b6-cd1bd31622dc - url: - type: string - format: uri - description: The URL of the test target for this run. - example: https://example.com - context: - $ref: '#/components/schemas/ExecutionContext' - environmentName: - type: string - format: environment name - description: the environment name you want to run your test against - default: default - variablesToOverwrite: - $ref: '#/components/schemas/Variables' - tags: - type: array - items: - type: string - example: - - tag1 - - tag2 - default: [] - description: The tags to filter the test cases by. - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - testCaseVersionIds: - type: array - items: - type: string - format: uuid - required: - - testTargetId - - url - - context - TestResult: - type: object - example: - id: 333d3f57-00cf-4ce9-8d34-89093a08681a - testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:03:12.345Z' - status: PASSED - errorMessage: null - traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip - properties: - id: - type: string - format: uuid - description: Unique identifier for the test result. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: Unique identifier of the test report this result belongs to. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: - type: string - format: uuid - description: Unique identifier of the test case version this result belongs to. - example: 5b844cf1-d597-4048-9e74-7c0f9ce3e2ee - createdAt: - type: string - format: date-time - description: The timestamp when the test result was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the test result was last updated. - example: '2024-09-06T13:01:51.686Z' - status: - type: string - enum: - - WAITING - - PASSED - - FAILED - - ERROR - description: The status of the specific test result, will be WAITING as long as the result is running, FAILED if the execution failed, PASSED if it succeeded, and ERROR if an internal error occurred. - errorMessage: - type: string - nullable: true - description: The error that has occurred during execution - only set if the status is FAILED or ERROR. - example: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' - traceUrl: - type: string - nullable: true - description: Link to the playwright trace of the test execution - only set once the test result is finished (PASSED or FAILED). - example: https://storage.googleapis.com/automagically-traces/826c15af-644b-4b28-89b4-f50ff34e46b7-trace.zip - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - TestReport: - type: object - required: - - id - - testTargetId - - createdAt - - updatedAt - - executionUrl - - status - - context - properties: - id: - type: string - format: uuid - description: Unique identifier for the test report. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: The unique identifier of the test target. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: - type: string - format: date-time - description: The timestamp when the test report was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the test report was last updated. - example: '2024-09-06T13:01:51.686Z' - executionUrl: - type: string - format: uri - description: The URL where the test execution was performed. - example: https://en.wikipedia.org/ - status: - type: string - enum: - - WAITING - - PASSED - - FAILED - description: The status of the test report, will be WAITING as long as any result is running, FAILED if the report is done but has any failed result and PASSED if all test results are done and successful - context: - $ref: '#/components/schemas/ExecutionContext' - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - testResults: - type: array - items: - $ref: '#/components/schemas/TestResult' - PrivateLocationInfo: - type: array - items: - type: object - properties: - status: - type: string - enum: - - OFFLINE - - ONLINE - address: - type: string - format: uri - example: https://example.com:3128 - name: - type: string - example: my-private-location - required: - - status - - address - - name - TestExecutionResponse: - type: object - required: - - testReportUrl - - testReport - properties: - testReportUrl: - type: string - format: uri - description: The URL where the test report can be accessed. - example: https://app.octomind.dev/testreports/826c15af-644b-4b28-89b4-f50ff34e46b7 - testReport: - $ref: '#/components/schemas/TestReport' - TestCasesResponse: - type: array - items: - $ref: '#/components/schemas/TestCaseResponse' - TestCaseResponse: - type: object - properties: - version: - type: string - enum: - - '1' - description: The version of the test case response. - id: - type: string - format: uuid - description: The ID of the test case. - testTargetId: - type: string - format: uuid - type: - type: string - nullable: true - elements: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - interaction: - type: object - nullable: true - properties: - id: - type: string - format: uuid - action: - type: string - enum: - - EXTRACT - - ENTER_TEXT - - CLICK - - SELECT_OPTION - - TYPE_TEXT - - KEY_PRESS - - HOVER - - UPLOAD - - GO_TO - - DRAG_AND_DROP - - CLOSE_PAGE - - OPEN_EMAIL - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - assertion: - type: object - nullable: true - properties: - id: - type: string - format: uuid - expectation: - type: string - enum: - - VISIBLE - - NOT_VISIBLE - - TO_BE_CHECKED - - NOT_TO_BE_CHECKED - - TO_HAVE_VALUE - - TO_CONTAIN_TEXT - - TO_HAVE_STYLE - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - scrollState: - type: object - nullable: true - selectors: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - selector: - type: string - selectorType: - type: string - enum: - - TEXT - - LABEL - - PLACEHOLDER - - ROLE - options: - type: object - nullable: true - properties: - name: - type: string - testCaseElementId: - type: string - format: uuid - scrollStateId: - type: string - nullable: true - testCaseId: - type: string - format: uuid - ignoreFailure: - type: boolean - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - description: - type: string - status: - type: string - enum: - - ENABLED - - DRAFT - externalId: - type: string - nullable: true - entryPointUrlPath: - type: string - nullable: true - tags: - type: array - items: - type: string - createdBy: - type: string - enum: - - EDIT - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - prerequisiteId: - type: string - nullable: true - proposalRunId: - type: string - nullable: true - folderId: - type: string - nullable: true - teardownTestCaseId: - type: string - nullable: true - discovery: - type: object - nullable: true - properties: - updatedAt: - type: string - format: date-time - freePrompt: - type: string - traceUrl: - type: string - nullable: true - status: - type: string - enum: - - CREATED - - IN_PROGRESS - - ERROR - - SUCCESS - - FAILED - - INCOMPLETE - - QUEUED - - STOPPED - - OUTDATED - - PAUSED - abortCause: - type: string - nullable: true - message: - type: string - nullable: true - required: - - id - - testTargetId - SuccessResponse: - type: object - properties: - success: - type: boolean - description: Indicates whether the operation was successful. - example: true - UnregisterRequest: - type: object - properties: - name: - type: string - RegisterRequest: - type: object - properties: - name: - type: string - registrationData: - type: object - properties: - proxypass: - type: string - example: secret22 - proxyuser: - type: string - example: user - address: - type: string - description: the address of the remote endpoint. IP and port - example: 34.45.23.22:23455 - EnvironmentsResponse: - type: array - items: - $ref: '#/components/schemas/EnvironmentResponse' - EnvironmentSimpleResponse: - type: object - properties: - id: - type: string - format: uuid - type: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - discoveryUrl: - type: string - format: url - example: https://example.com - email: - type: string - example: user@example.com - description: The 2FA email of the environment to test email flows. - EnvironmentResponse: - type: object - properties: - id: - type: string - format: uuid - name: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - testTargetId: - type: string - format: uuid - updatedAt: - type: string - format: date-time - type: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - discoveryUrl: - type: string - format: url - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - updatedAt: - type: string - format: date-time - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - updatedAt: - type: string - format: date-time - nullable: true - privateLocation: - type: object - properties: - id: - type: string - format: uuid - name: - type: string - status: - type: string - type: - type: string - required: - - id - - name - - testTargetId - - type - CreateTestTargetBody: - type: object - properties: - testTarget: - type: object - properties: - app: - type: string - description: The app name or project name of the test target - discoveryUrl: - type: string - format: uri - description: The discovery URL of the test target. This is the URL that a discovery tool can use to discover the test target. - required: - - app - - discoveryUrl - TestTargetsResponse: - type: array - items: - $ref: '#/components/schemas/TestTargetResponse' - TestTargetResponse: - type: object - properties: - id: - type: string - format: uuid - app: - type: string - description: The app name or project name of the test target - tags: - type: array - items: - type: string - nullable: true - environments: - type: array - items: - $ref: '#/components/schemas/EnvironmentSimpleResponse' - description: The environments of the test target - required: - - id - - app - TestTargetUpdateRequest: - type: object - properties: - app: - type: string - nullable: true - description: The app name or project name of the test target - testIdAttribute: - type: string - nullable: true - description: The attribute name of the test ID - example: test-automation-id - testRailIntegration: - type: object - properties: - domain: - type: string - description: The domain of the TestRail instance - example: https://mycompany.testrail.io - username: - type: string - description: The username for the TestRail instance - example: user - projectId: - type: string - description: The project ID for the TestRail instance - example: '123' - apiKey: - type: string - description: The TestRail API key for the TestRail instance - example: '123123123' - nullable: true - timeoutPerStep: - type: number - format: int32 - minimum: 5000 - maximum: 30000 - description: The timeout per step in milliseconds - ZodResponse: - type: array - items: - type: object - properties: - code: - type: string - example: invalid_type - description: What error code happened while parsing the request - expected: - type: string - description: What the expected type was - example: object - received: - type: string - description: What the actual passed type was - example: string - path: - type: array - items: - type: string - description: The json path to the wrong parameter - example: key - message: - type: string - description: Human-readable message of the error that occurred while parsing. - example: Expected object, received string - ExternalBatchGenerationBody: - type: object - properties: - prompt: - type: string - description: Prompt to generate the test cases in this batch generation - imageUrls: - type: array - items: - type: string - description: Image URLs to generate the test cases in this batch generation - entryPointUrlPath: - type: string - nullable: true - description: Entry point URL path, where the batch generation will start - environmentId: - type: string - nullable: true - description: Environment ID, where the batch generation will be executed - prerequisiteId: - type: string - nullable: true - description: Prerequisite ID, which will be executed before the batch generation - baseUrl: - type: string - nullable: true - description: Base URL, where the batch generation will be executed - context: - $ref: '#/components/schemas/ExecutionContext' - BatchGenerationResponse: - type: object - properties: - batchGenerationId: - type: string - format: uuid - description: Unique identifier for the batch generation - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - Notification: - type: object - properties: - id: - type: string - format: uuid - description: Unique identifier for the event. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: The unique identifier of the test target this event belongs to. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: - type: string - format: date-time - description: The timestamp when the event was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the event was last updated. - example: '2024-09-06T13:01:51.686Z' - payload: - type: object - description: JSON payload containing event-specific data. - example: - testCaseId: 5deeef76-f37a-418b-8101-7d69529fa561 - type: - type: string - enum: - - VALIDATION_PASSED - - VALIDATION_FAILED - - DISCOVERY_FINISHED - - PROPOSAL_SUCCESS - - PROPOSAL_FAILED - - REPORT_EXECUTION_FINISHED - description: The type of event that occurred. - example: VALIDATION_PASSED - ack: - type: string - enum: - - IN_WEB_APP - description: Optional acknowledgment status of the event. - example: IN_WEB_APP - nullable: true - required: - - id - - testTargetId - - createdAt - - updatedAt - - payload - - type - ExternalDiscoveryBody: - type: object - properties: - name: - type: string - description: Name of the discovered test case - example: Login Form Validation Test - entryPointUrlPath: - type: string - description: Entry point URL path of the discovered test case - example: /login - prerequisiteName: - type: string - description: Prerequisite test case name - example: Cookie Banner Acceptance - externalId: - type: string - description: External ID of the discovered test case - example: ext-test-001 - tagNames: - type: array - items: - type: string - description: Tags to assign to the discovered test case - example: - - authentication - - forms - - validation - prompt: - type: string - description: Prompt to generate the discovered test case - example: Test the login form with valid and invalid credentials - folderName: - type: string - description: Folder name of the discovered test case - example: Authentication Tests - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - description: Type of the discovered test case - example: LOGIN - required: - - name - - prompt - DiscoveryResponse: - type: object - required: - - discoveryId - - testCaseId - properties: - discoveryId: - type: string - format: uuid - description: The ID of the created discovery - testCaseId: - type: string - format: uuid - description: The ID of the associated test case - ExecutionContext: - oneOf: - - type: object - properties: - source: - type: string - enum: - - github - example: github - issueNumber: - type: integer - nullable: true - example: 123 - ref: - type: string - nullable: true - example: refs/heads/main - sha: - type: string - nullable: true - example: abc123def456 - repo: - type: string - example: my-repo - owner: - type: string - example: repo-owner - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - nodeId: - type: string - nullable: true - example: node-123 - - type: object - properties: - source: - type: string - enum: - - azureDevOps - example: azureDevOps - accessToken: - type: string - example: token123 - organization: - type: string - example: my-org - project: - type: string - example: my-project - repositoryId: - type: string - example: repo-123 - sha: - type: string - nullable: true - example: abc123def456 - ref: - type: string - nullable: true - example: refs/heads/main - pullRequestId: - type: integer - nullable: true - example: 101 - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - threadId: - type: string - nullable: true - example: thread-123 - - type: object - properties: - source: - type: string - enum: - - discovery - example: discovery - description: - type: string - example: A discovery test - triggeredBy: - oneOf: - - type: object - properties: - type: - type: string - enum: - - INITIAL - example: INITIAL - - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - manual - example: manual - description: - type: string - example: A manual trigger - triggeredBy: - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - scheduled - example: scheduled - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - proposal - example: proposal - description: - type: string - example: A proposal trigger - triggeredBy: - oneOf: - - type: object - properties: - type: - type: string - enum: - - INITIAL - example: INITIAL - - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - Variables: - type: object - additionalProperties: - type: array - items: - type: string - description: The variables to overwrite exclusively for this test run. - example: - SPACE_ID: - - 64ee32b4-7365-47a6-b5b0-2903b6ad849d - TestCaseElement: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - interaction: - type: object - nullable: true - properties: - id: - type: string - format: uuid - action: - type: string - enum: - - EXTRACT - - ENTER_TEXT - - CLICK - - SELECT_OPTION - - TYPE_TEXT - - KEY_PRESS - - HOVER - - UPLOAD - - GO_TO - - DRAG_AND_DROP - - CLOSE_PAGE - - OPEN_EMAIL - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - assertion: - type: object - nullable: true - properties: - id: - type: string - format: uuid - expectation: - type: string - enum: - - VISIBLE - - NOT_VISIBLE - - TO_BE_CHECKED - - NOT_TO_BE_CHECKED - - TO_HAVE_VALUE - - TO_CONTAIN_TEXT - - TO_HAVE_STYLE - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - selectors: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - selector: - type: string - selectorType: - type: string - enum: - - TEXT - - LABEL - - PLACEHOLDER - - ROLE - options: - type: object - nullable: true - testCaseElementId: - type: string - format: uuid - testCaseId: - type: string - format: uuid - ignoreFailure: - type: boolean - TestTargetSyncSchema: - title: TestTargetSyncSchema - description: schema for import and export of test cases - type: object - properties: - testCases: - type: array - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false - required: - - testCases - additionalProperties: false - TestTargetCodeSchema: - title: TestTargetCodeSchema - description: schema for export of test cases as code - type: object - properties: - executionUrl: - type: string - format: uri - environmentId: - type: string - format: uuid - testCases: - type: array - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false - required: - - executionUrl - - testCases - additionalProperties: false - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false diff --git a/package.json b/package.json index 24bece3..0b33e1c 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,7 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", - "files": [ - "dist", - "src", - "README.md", - "LICENSE", - "package.json" - ], + "files": ["dist", "src", "README.md", "LICENSE", "package.json"], "engines": { "node": ">=22.0.0" }, @@ -34,7 +28,7 @@ "dev:local": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.local.json OCTOMIND_API_URL=http://localhost:3000/api tsx src/index.ts", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.prod.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", - "apigen": "openapi-typescript openapi.yaml --output src/api.ts && orval", + "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", "build": "pnpm apigen && npx genversion -des src/version.ts && pnpm gendoc && tsc --project tsconfig.build.json", "octomind": "pnpm apigen && tsx src/index.ts", "test": "pnpm apigen && npx genversion -des src/version.ts && vitest", From 8f6671281927fd33933f95003395bad3ec902df4 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Wed, 14 Jan 2026 16:05:30 +0100 Subject: [PATCH 31/36] lockfile --- pnpm-lock.yaml | 235 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f48cfa..a3600c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,18 @@ importers: commander: specifier: 14.0.2 version: 14.0.2 + diff: + specifier: 8.0.3 + version: 8.0.3 + open: + specifier: 11.0.0 + version: 11.0.0 openapi-fetch: specifier: 0.15.0 version: 0.15.0 + ora: + specifier: 9.0.0 + version: 9.0.0 otplib: specifier: 13.1.0 version: 13.1.0 @@ -948,6 +957,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1006,6 +1019,10 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1033,6 +1050,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -1047,6 +1068,14 @@ packages: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-width@2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} @@ -1121,10 +1150,22 @@ packages: supports-color: optional: true + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.4.0: + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1133,6 +1174,10 @@ packages: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1329,6 +1374,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1469,6 +1518,11 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1493,6 +1547,19 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -1537,6 +1604,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -1549,6 +1620,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -1639,6 +1714,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + loglevel-plugin-prefix@0.8.4: resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} @@ -1682,6 +1761,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1781,6 +1864,14 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + openapi-fetch@0.15.0: resolution: {integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==} @@ -1799,6 +1890,10 @@ packages: openapi3-ts@4.5.0: resolution: {integrity: sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==} + ora@9.0.0: + resolution: {integrity: sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==} + engines: {node: '>=20'} + orval@7.18.0: resolution: {integrity: sha512-mPGSQeAAxhvRoxMKn22vmmtFa5eoJiwTBUSsrwKjKjC+rJdA3eWOLRiU1ICq2lep22S+3qpEy5WizP5FW26+rg==} engines: {node: '>=22.18.0'} @@ -1886,6 +1981,10 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -1929,6 +2028,10 @@ packages: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1938,6 +2041,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -2034,6 +2141,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-eval@1.0.1: resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==} engines: {node: '>=12'} @@ -2055,6 +2166,10 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2071,6 +2186,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@8.1.0: + resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + engines: {node: '>=20'} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -2098,6 +2217,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -2397,6 +2520,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2425,6 +2552,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zod@4.3.5: resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} @@ -3275,6 +3406,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -3331,6 +3464,10 @@ snapshots: dependencies: fill-range: 7.1.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3363,6 +3500,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + change-case@5.4.4: {} chardet@0.7.0: {} @@ -3375,6 +3514,12 @@ snapshots: dependencies: restore-cursor: 2.0.0 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + cli-width@2.2.1: {} cliui@8.0.1: @@ -3441,12 +3586,21 @@ snapshots: optionalDependencies: supports-color: 10.2.2 + default-browser-id@5.0.1: {} + + default-browser@5.4.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -3455,6 +3609,8 @@ snapshots: dependency-graph@0.11.0: {} + diff@8.0.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -3757,6 +3913,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3918,6 +4076,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -3940,6 +4100,14 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -3981,6 +4149,8 @@ snapshots: dependencies: which-typed-array: 1.1.19 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -3992,6 +4162,10 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -4062,6 +4236,11 @@ snapshots: lodash@4.17.21: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + loglevel-plugin-prefix@0.8.4: {} loglevel@1.9.2: {} @@ -4098,6 +4277,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -4208,6 +4389,19 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@11.0.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + openapi-fetch@0.15.0: dependencies: openapi-typescript-helpers: 0.0.15 @@ -4230,6 +4424,18 @@ snapshots: dependencies: yaml: 2.8.2 + ora@9.0.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.2.2 + string-width: 8.1.0 + strip-ansi: 7.1.2 + orval@7.18.0(openapi-types@12.1.3)(typescript@5.9.3): dependencies: '@apidevtools/swagger-parser': 12.1.0(openapi-types@12.1.3) @@ -4337,6 +4543,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + powershell-utils@0.1.0: {} + process-nextick-args@2.0.1: {} punycode.js@2.3.1: {} @@ -4388,6 +4596,11 @@ snapshots: onetime: 2.0.1 signal-exit: 3.0.7 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.1.0: {} rollup@4.55.1: @@ -4421,6 +4634,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 + run-applescript@7.1.0: {} + run-async@2.4.1: {} run-parallel@1.2.0: @@ -4544,6 +4759,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + simple-eval@1.0.1: dependencies: jsep: 1.4.0 @@ -4564,6 +4781,8 @@ snapshots: std-env@3.10.0: {} + stdin-discarder@0.2.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -4582,6 +4801,11 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@8.1.0: + dependencies: + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -4621,6 +4845,10 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + strip-final-newline@2.0.0: {} supports-color@10.2.2: {} @@ -4927,6 +5155,11 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.0 + powershell-utils: 0.1.0 + y18n@5.0.8: {} yaml-ast-parser@0.0.43: {} @@ -4949,4 +5182,6 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + zod@4.3.5: {} From 3fd0d482e62ea288276f72573b3d0060d33420a6 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 16:23:50 +0100 Subject: [PATCH 32/36] remove openapi.yaml --- openapi.yaml | 4735 -------------------------------------------------- 1 file changed, 4735 deletions(-) delete mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml deleted file mode 100644 index e3774b0..0000000 --- a/openapi.yaml +++ /dev/null @@ -1,4735 +0,0 @@ -openapi: 3.1.0 -info: - title: Octomind external API - description: Octomind API that allows you to execute test cases, fetch reports and register private location workers by providing a URL and an ID. - version: 1.0.0 - contact: - name: API Support - url: https://octomind.dev/docs - email: support@octomind.dev -servers: - - url: https://app.octomind.dev/api - description: Main API Endpoint -tags: - - name: Test targets - description: Operations for managing test targets - - name: Execute - description: Operations for executing tests - - name: Environments - description: Operations for managing test environments - - name: Reports - description: Operations for retrieving test reports - - name: Notifications - description: Operations for retrieving notifications - - name: Private locations - description: Operations for managing private locations - - name: Test Cases - description: Operations for retrieving test cases - - name: Discoveries - description: Operations for creating test discoveries -externalDocs: - description: Find out more - url: https://octomind.dev/docs/ -paths: - /apiKey/v3/test-targets: - get: - summary: Retrieve all test targets - description: Gets a list of test targets. - operationId: getTestTargets - tags: - - Test targets - security: - - ApiKeyAuth: [] - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetsResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - post: - summary: Create a new test target - description: Creates a new test target. - operationId: createTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTestTargetBody' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}: - get: - summary: Retrieve a test target - description: Gets a test target by ID. - operationId: getTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to fetch - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - patch: - summary: Update a test target - description: Updates a test target by ID. - operationId: updateTestTarget - tags: - - Test targets - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to update - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetUpdateRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - delete: - summary: Delete a test target - description: Deletes a test target by ID. - operationId: deleteTestTarget - tags: - - Test targets - security: - - ApiKeyAuth: [] - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to delete - responses: - '204': - description: OK - '401': - description: Invalid or missing API key - '404': - description: Test target not found - '500': - description: Internal server error - /apiKey/v3/execute: - post: - summary: Execute tests of the given test target - description: | - This endpoint triggers a test execution by sending an test target id, an URL and optionally tags, an environment and variables. - operationId: executeTests - tags: - - Execute - security: - - ApiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetExecutionRequest' - responses: - '200': - description: Test executed successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestExecutionResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/batch-generations: - post: - summary: Create a batch generation - description: Creates a batch generation for the given test target. - operationId: createBatchGeneration - tags: - - Discoveries - security: - - ApiKeyAuth: [] - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalBatchGenerationBody' - responses: - '200': - description: Batch generation created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/BatchGenerationResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/config: - get: - summary: Retrieve test target configuration - description: Get the test target configuration for a specific environment - operationId: getTestTargetConfig - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: url - in: query - required: true - schema: - type: string - format: uri - description: The execution URL for the test target - - name: outputDir - in: query - required: true - schema: - type: string - description: The directory where test output will be stored - - name: headless - in: query - required: false - schema: - type: string - description: Whether to run tests in headless mode (true/false) - - name: environmentId - in: query - required: false - schema: - type: string - format: uuid - description: Optional ID of the environment to use (defaults to the test target's default environment if not provided) - responses: - '200': - description: Test target configuration retrieved successfully - content: - text/plain: - schema: - type: string - description: The test target configuration as plain text - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Environment not found - '405': - description: Method not allowed - /apiKey/v3/test-targets/{testTargetId}/environments: - post: - summary: Create an environment - description: Create a custom environment. - operationId: createEnvironment - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the environment belongs to - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - discoveryUrl: - type: string - format: url - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - nullable: true - privateLocationName: - type: string - description: name of the private location - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - responses: - '201': - description: environment created - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - get: - summary: Retrieve environments - description: get a list of all defined environments. - operationId: getEnvironments - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - responses: - '200': - description: environments - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentsResponse' - /apiKey/v3/test-targets/{testTargetId}/environments/{environmentId}: - patch: - summary: Update an environment - description: Updates an enviroment, all properties can be set separately - operationId: updateEnvironment - security: - - ApiKeyAuth: [] - tags: - - Environments - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the environment belongs to - - in: path - name: environmentId - required: true - schema: - type: string - format: uuid - description: ID of the environment to update - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - nullable: true - discoveryUrl: - type: string - format: url - nullable: true - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - nullable: true - privateLocationName: - type: string - nullable: true - description: name of the private location - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - responses: - '200': - description: Environment updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/EnvironmentResponse' - delete: - summary: Delete an environment - operationId: deleteEnvironment - description: deletes an enviroment. this operation is not reversable. - security: - - ApiKeyAuth: [] - tags: - - Environments - responses: - '200': - description: Environment deleted successfully - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - - in: path - name: environmentId - required: true - schema: - type: string - format: uuid - description: ID of the environment to update - /apiKey/v3/test-targets/{testTargetId}/test-reports/{testReportId}: - get: - summary: Retrieve information about a test report - description: Poll from within a CI-pipeline to wait for the completion of a report. - operationId: getTestReport - tags: - - Reports - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target to which the test report belongs to - - in: path - name: testReportId - required: true - schema: - type: string - format: uuid - description: ID of the test report to fetch - security: - - ApiKeyAuth: [] - responses: - '200': - description: Test Report information - content: - application/json: - schema: - $ref: '#/components/schemas/TestReport' - example: - id: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:05:23.412Z' - executionUrl: https://en.wikipedia.org/ - status: FAILED - context: - source: github - issueNumber: 123 - ref: refs/heads/main - sha: abc123def456 - repo: my-repo - owner: repo-owner - triggeredBy: - type: USER - userId: 3435918b-3d29-4ebd-8c68-9a540532f45a - nodeId: node-123 - testResults: - - id: 9876fedc-ba98-7654-3210-fedcba987654 - testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:03:12.345Z' - status: PASSED - errorMessage: null - traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip - - id: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: 5432fedc-ba98-7654-3210-fedcba543210 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:04:45.678Z' - status: FAILED - errorMessage: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' - traceUrl: https://storage.googleapis.com/automagically-traces/2a3b4c5d-6e7f-8g9h-0i1j-2k3l4m5n6o7p-trace.zip - - id: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: 1234abcd-ef56-7890-abcd-ef1234567890 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:05:23.412Z' - status: WAITING - errorMessage: null - traceUrl: null - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-reports: - get: - summary: Retrieve paginated information about test reports - description: Allow fetching the history of test reports for your test target. - operationId: getTestReports - tags: - - Reports - parameters: - - in: path - name: testTargetId - required: true - schema: - type: string - format: uuid - description: ID of the test target for which to fetch the history for - - in: query - name: key - required: false - schema: - type: object - properties: - createdAt: - type: string - format: date-time - description: The timestamp of the key of the next page to fetch - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) - example: '2024-09-06T13:01:51.686Z' - - in: query - name: filter - required: false - schema: - type: array - items: - type: object - properties: - key: - type: string - description: The name of the property to filter for, e.g. an environmentId - example: environmentId - operator: - type: string - enum: - - EQUALS - description: How to compare the property in question, only EQUALS is supported so far. - value: - type: string - format: uuid - description: The value to compare with to find matches. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - security: - - ApiKeyAuth: [] - responses: - '200': - description: Test Reports information - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/TestReport' - key: - type: object - properties: - createdAt: - type: string - format: date-time - description: The timestamp of the key of the next page to fetch - use this in the query when fetching the next page of reports - See [Keyset Pagination](https://use-the-index-luke.com/no-offset) - example: '2024-09-06T13:01:51.686Z' - hasNextPage: - type: boolean - description: If the query in question has another page to retrieve - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Invalid or missing API key - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/notifications: - get: - summary: Retrieve notifications - description: Get a list of notifications for a specific test target. - operationId: getNotifications - security: - - ApiKeyAuth: [] - tags: - - Notifications - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - responses: - '200': - description: List of notifications - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Notification' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/v1/private-location: - get: - security: - - ApiKeyAuth: [] - summary: Retrieve all private locations - description: gets a list of private location workers - operationId: getPrivateLocations - tags: - - Private locations - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PrivateLocationInfo' - /apiKey/v1/private-location/register: - put: - security: - - ApiKeyAuth: [] - summary: Register a private location - description: registers a private location worker - operationId: registerPrivateLocation - tags: - - Private locations - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/RegisterRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '404': - description: private location of that name not found - '500': - description: Internal server error - /apiKey/v1/private-location/unregister: - put: - security: - - ApiKeyAuth: [] - summary: Unregister a private location - description: Unregisters a private location worker. - operationId: unregisterPrivateLocation - tags: - - Private locations - externalDocs: - url: https://octomind.dev/docs/proxy/private-location - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UnregisterRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '404': - description: private location of that name not found - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-cases: - get: - summary: List test cases - description: Get a list of test cases for a specific test target with optional filtering - operationId: getTestCases - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: filter - in: query - required: false - schema: - type: string - description: | - JSON string containing filter criteria for test cases. The filter supports the following fields: - - testTargetId: Filter by test target ID - - description: Filter by test case description - - status: Filter by test case status (ENABLED, DISABLED, DRAFT, OUTDATED, DELETED, PROVISIONAL) - - runStatus: Filter by run status (ON, OFF) - - folderId: Filter by folder ID - - externalId: Filter by external ID - - AND: Logical AND operator for combining multiple conditions - - OR: Logical OR operator for combining multiple conditions - - NOT: Logical NOT operator for negating conditions - example: '{"status":"ENABLED","folderId":"some-folder-id","OR":[{"description":"Login Test"},{"externalId":"TEST-123"}]}' - responses: - '200': - description: List of test cases - content: - application/json: - schema: - $ref: '#/components/schemas/TestCasesResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}: - get: - summary: Retrieve a test case - description: Get detailed information about a specific test case - operationId: getTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - responses: - '200': - description: Test case details - content: - application/json: - schema: - $ref: '#/components/schemas/TestCaseResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target or test case not found - patch: - summary: Update a test case - description: Update specific properties of a test case - operationId: patchTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case to update - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - elements: - type: array - items: - $ref: '#/components/schemas/TestCaseElement' - description: - type: string - entryPointUrlPath: - type: string - nullable: true - status: - type: string - enum: - - ENABLED - - DISABLED - - DRAFT - - OUTDATED - - PROVISIONAL - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - folderName: - type: string - nullable: true - interactionStatus: - type: string - enum: - - NEW - - EDITED - - APPROVED - - REJECTED - createBackendDiscoveryPrompt: - type: string - assignedTagNames: - type: array - items: - type: string - externalId: - type: string - nullable: true - responses: - '200': - description: Updated test case - content: - application/json: - schema: - type: object - properties: - id: - type: string - format: uuid - testTargetId: - type: string - format: uuid - description: - type: string - status: - type: string - enum: - - ENABLED - - DISABLED - - DRAFT - - OUTDATED - - PROVISIONAL - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - entryPointUrlPath: - type: string - nullable: true - elements: - type: array - items: - type: object - folderId: - type: string - nullable: true - externalId: - type: string - nullable: true - tags: - type: array - items: - type: string - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case, folder or tag not found - delete: - summary: Delete a test case - description: Delete a specific test case - operationId: deleteTestCase - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - responses: - '200': - description: Test case deleted successfully - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: - - deleted - description: Status of the deletion operation - required: - - status - '400': - description: Bad request - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/ZodResponse' - - type: object - properties: - status: - type: string - enum: - - has dependencies - description: Indicates the test case has dependencies - dependencyIds: - type: array - items: - type: string - format: uuid - description: List of test case IDs that depend on this test case - required: - - status - - dependencyIds - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case not found - '500': - description: Internal server error - content: - application/json: - schema: - type: object - properties: - status: - type: string - enum: - - error - description: Indicates an error occurred - error: - type: string - description: Error message - required: - - status - - error - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/elements/{elementId}: - patch: - summary: Update a test case element - description: Update a the locator line of a specific test case element - operationId: updateTestCaseElement - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: elementId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case element - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - locatorLine: - description: The locator line of the test case element - type: string - required: - - locatorLine - responses: - '200': - description: Test case element updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestCaseElement' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target, test case or test case element not found - '500': - description: Internal server error - /apiKey/v3/test-targets/{testTargetId}/test-cases/{testCaseId}/code: - get: - summary: Retrieve code for a test case - description: Get the code representation of a specific test case - operationId: getTestCaseCode - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: executionUrl - in: query - required: true - schema: - type: string - format: url - description: URL of the app to test - - name: environmentId - in: query - required: false - schema: - type: string - format: uuid - description: Optional ID of the environment to use - responses: - '200': - description: Test case code retrieved successfully - content: - application/json: - schema: - type: object - properties: - testCode: - type: string - description: The code representation of the test case - required: - - testCode - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target, test case, or test code not found - /apiKey/beta/test-targets/{testTargetId}/pull: - get: - summary: Pull all test case from a test target to use it locally as json or yaml and work with the files directly. There is also a "opposite" endpoint to push the files back to the test target. - description: Get the pull data for a specific test target - operationId: getTestTargetPullData - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - responses: - '200': - description: Test target pull data retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/code: - post: - summary: Get TestSuite code - description: Retrieve the typescript code in a zip file for a set of yaml based test cases. This code can then be executed by playwright. - operationId: getTestTargetCode - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetCodeSchema' - responses: - '200': - description: Test target code retrieved successfully - content: - application/zip: - schema: - type: string - format: binary - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/push: - post: - summary: Push test target - description: Pushes all test cases with the specified schema to the test target. There is also a "opposite" endpoint to pull the files back to the local machine. - operationId: pushTestTarget - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - responses: - '200': - description: Test target pushed successfully - content: - application/json: - schema: - type: object - required: - - success - - versionIds - properties: - success: - type: boolean - versionIds: - type: array - items: - type: string - format: uuid - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/draft/push: - post: - summary: Push draft test target - description: Push all test cases from a local representation to a test target as a draft. - operationId: pushTestTargetDraft - security: - - ApiKeyAuth: [] - tags: - - Test targets - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TestTargetSyncSchema' - responses: - '200': - description: Test target pushed successfully - content: - application/json: - schema: - type: object - required: - - success - - versionIds - - versionIdByStableId - properties: - success: - type: boolean - versionIds: - type: array - items: - type: string - format: uuid - description: List of test case IDs that were pushed to the test target - versionIdByStableId: - type: object - additionalProperties: - type: string - format: uuid - example: - 5afe4190-5900-47d0-8cfd-bbb7dce2460e: 48c9e7d0-67e3-4806-b255-3b9f79fa9639 - description: Version IDs per stable id that were pushed to the test target - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - /apiKey/beta/test-targets/{testTargetId}/test-cases/{testCaseId}/versions/{versionId}: - get: - summary: Retrieve a specific test case version - description: Get detailed information about a specific version of a test case, including its local editing status. - operationId: getTestCaseVersion - security: - - ApiKeyAuth: [] - tags: - - Test Cases - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - - name: testCaseId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test case - - name: versionId - in: path - required: true - schema: - type: string - format: uuid - description: The version ID of the test case - responses: - '200': - description: Test case version details - content: - application/json: - schema: - allOf: - - $ref: '#/components/schemas/items' - - type: object - required: - - versionId - - localEditingStatus - properties: - versionId: - type: string - format: uuid - description: The version ID of this test case - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - nullable: true - description: The local editing status of this test case version - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test case not found - /apiKey/v3/test-targets/{testTargetId}/discoveries: - post: - summary: Create a discovery - description: Create a new test case discovery with a given name and prompt - operationId: createDiscovery - security: - - ApiKeyAuth: [] - tags: - - Discoveries - parameters: - - name: testTargetId - in: path - required: true - schema: - type: string - format: uuid - description: The ID of the test target - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalDiscoveryBody' - responses: - '200': - description: Discovery created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/DiscoveryResponse' - '400': - description: Invalid request parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ZodResponse' - '401': - description: Unauthorized - Invalid or missing API key - '404': - description: Test target not found - '500': - description: Internal server error -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - schemas: - Breakpoint: - type: string - enum: - - MOBILE - - TABLET - - DESKTOP - description: The breakpoint to run the test cases against. - example: DESKTOP - default: DESKTOP - Browser: - type: string - enum: - - CHROMIUM - - FIREFOX - - SAFARI - description: The browser to run the test cases against. - example: CHROMIUM - default: CHROMIUM - TestTargetExecutionRequest: - type: object - properties: - testTargetId: - type: string - format: uuid - description: Unique identifier for the testTarget. - example: 2e2bb27b-a19c-47ce-a9b6-cd1bd31622dc - url: - type: string - format: uri - description: The URL of the test target for this run. - example: https://example.com - context: - $ref: '#/components/schemas/ExecutionContext' - environmentName: - type: string - format: environment name - description: the environment name you want to run your test against - default: default - variablesToOverwrite: - $ref: '#/components/schemas/Variables' - tags: - type: array - items: - type: string - example: - - tag1 - - tag2 - default: [] - description: The tags to filter the test cases by. - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - testCaseVersionIds: - type: array - items: - type: string - format: uuid - required: - - testTargetId - - url - - context - TestResult: - type: object - example: - id: 333d3f57-00cf-4ce9-8d34-89093a08681a - testCaseVersionId: 9876fedc-ba98-7654-3210-fedcba987654 - testTargetId: 3435918b-3d29-4ebd-8c68-9a540532f45a - testReportId: 826c15af-644b-4b28-89b4-f50ff34e46b7 - createdAt: '2024-09-06T13:01:51.686Z' - updatedAt: '2024-09-06T13:03:12.345Z' - status: PASSED - errorMessage: null - traceUrl: https://storage.googleapis.com/automagically-traces/7a1b2c3d-4e5f-6g7h-8i9j-0k1l2m3n4o5p-trace.zip - properties: - id: - type: string - format: uuid - description: Unique identifier for the test result. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: Unique identifier of the test report this result belongs to. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - testCaseVersionId: - type: string - format: uuid - description: Unique identifier of the test case version this result belongs to. - example: 5b844cf1-d597-4048-9e74-7c0f9ce3e2ee - createdAt: - type: string - format: date-time - description: The timestamp when the test result was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the test result was last updated. - example: '2024-09-06T13:01:51.686Z' - status: - type: string - enum: - - WAITING - - PASSED - - FAILED - - ERROR - description: The status of the specific test result, will be WAITING as long as the result is running, FAILED if the execution failed, PASSED if it succeeded, and ERROR if an internal error occurred. - errorMessage: - type: string - nullable: true - description: The error that has occurred during execution - only set if the status is FAILED or ERROR. - example: 'TimeoutError: locator.click: Timeout 30000ms exceeded.' - traceUrl: - type: string - nullable: true - description: Link to the playwright trace of the test execution - only set once the test result is finished (PASSED or FAILED). - example: https://storage.googleapis.com/automagically-traces/826c15af-644b-4b28-89b4-f50ff34e46b7-trace.zip - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - TestReport: - type: object - required: - - id - - testTargetId - - createdAt - - updatedAt - - executionUrl - - status - - context - properties: - id: - type: string - format: uuid - description: Unique identifier for the test report. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: The unique identifier of the test target. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: - type: string - format: date-time - description: The timestamp when the test report was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the test report was last updated. - example: '2024-09-06T13:01:51.686Z' - executionUrl: - type: string - format: uri - description: The URL where the test execution was performed. - example: https://en.wikipedia.org/ - status: - type: string - enum: - - WAITING - - PASSED - - FAILED - description: The status of the test report, will be WAITING as long as any result is running, FAILED if the report is done but has any failed result and PASSED if all test results are done and successful - context: - $ref: '#/components/schemas/ExecutionContext' - breakpoint: - $ref: '#/components/schemas/Breakpoint' - browser: - $ref: '#/components/schemas/Browser' - testResults: - type: array - items: - $ref: '#/components/schemas/TestResult' - PrivateLocationInfo: - type: array - items: - type: object - properties: - status: - type: string - enum: - - OFFLINE - - ONLINE - address: - type: string - format: uri - example: https://example.com:3128 - name: - type: string - example: my-private-location - required: - - status - - address - - name - TestExecutionResponse: - type: object - required: - - testReportUrl - - testReport - properties: - testReportUrl: - type: string - format: uri - description: The URL where the test report can be accessed. - example: https://app.octomind.dev/testreports/826c15af-644b-4b28-89b4-f50ff34e46b7 - testReport: - $ref: '#/components/schemas/TestReport' - TestCasesResponse: - type: array - items: - $ref: '#/components/schemas/TestCaseResponse' - TestCaseResponse: - type: object - properties: - version: - type: string - enum: - - '1' - description: The version of the test case response. - id: - type: string - format: uuid - description: The ID of the test case. - testTargetId: - type: string - format: uuid - type: - type: string - nullable: true - elements: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - interaction: - type: object - nullable: true - properties: - id: - type: string - format: uuid - action: - type: string - enum: - - EXTRACT - - ENTER_TEXT - - CLICK - - SELECT_OPTION - - TYPE_TEXT - - KEY_PRESS - - HOVER - - UPLOAD - - GO_TO - - DRAG_AND_DROP - - CLOSE_PAGE - - OPEN_EMAIL - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - assertion: - type: object - nullable: true - properties: - id: - type: string - format: uuid - expectation: - type: string - enum: - - VISIBLE - - NOT_VISIBLE - - TO_BE_CHECKED - - NOT_TO_BE_CHECKED - - TO_HAVE_VALUE - - TO_CONTAIN_TEXT - - TO_HAVE_STYLE - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - scrollState: - type: object - nullable: true - selectors: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - selector: - type: string - selectorType: - type: string - enum: - - TEXT - - LABEL - - PLACEHOLDER - - ROLE - options: - type: object - nullable: true - properties: - name: - type: string - testCaseElementId: - type: string - format: uuid - scrollStateId: - type: string - nullable: true - testCaseId: - type: string - format: uuid - ignoreFailure: - type: boolean - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - description: - type: string - status: - type: string - enum: - - ENABLED - - DRAFT - externalId: - type: string - nullable: true - entryPointUrlPath: - type: string - nullable: true - tags: - type: array - items: - type: string - createdBy: - type: string - enum: - - EDIT - runStatus: - type: string - enum: - - 'ON' - - 'OFF' - prerequisiteId: - type: string - nullable: true - proposalRunId: - type: string - nullable: true - folderId: - type: string - nullable: true - teardownTestCaseId: - type: string - nullable: true - discovery: - type: object - nullable: true - properties: - updatedAt: - type: string - format: date-time - freePrompt: - type: string - traceUrl: - type: string - nullable: true - status: - type: string - enum: - - CREATED - - IN_PROGRESS - - ERROR - - SUCCESS - - FAILED - - INCOMPLETE - - QUEUED - - STOPPED - - OUTDATED - - PAUSED - abortCause: - type: string - nullable: true - message: - type: string - nullable: true - required: - - id - - testTargetId - SuccessResponse: - type: object - properties: - success: - type: boolean - description: Indicates whether the operation was successful. - example: true - UnregisterRequest: - type: object - properties: - name: - type: string - RegisterRequest: - type: object - properties: - name: - type: string - registrationData: - type: object - properties: - proxypass: - type: string - example: secret22 - proxyuser: - type: string - example: user - address: - type: string - description: the address of the remote endpoint. IP and port - example: 34.45.23.22:23455 - EnvironmentsResponse: - type: array - items: - $ref: '#/components/schemas/EnvironmentResponse' - EnvironmentSimpleResponse: - type: object - properties: - id: - type: string - format: uuid - type: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - discoveryUrl: - type: string - format: url - example: https://example.com - email: - type: string - example: user@example.com - description: The 2FA email of the environment to test email flows. - EnvironmentResponse: - type: object - properties: - id: - type: string - format: uuid - name: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - testTargetId: - type: string - format: uuid - updatedAt: - type: string - format: date-time - type: - type: string - enum: - - DEFAULT - - ADDITIONAL - example: DEFAULT - discoveryUrl: - type: string - format: url - additionalHeaderFields: - type: object - additionalProperties: - type: string - nullable: true - testAccount: - type: object - properties: - username: - type: string - password: - type: string - otpInitializerKey: - type: string - nullable: true - updatedAt: - type: string - format: date-time - nullable: true - basicAuth: - type: object - properties: - username: - type: string - password: - type: string - updatedAt: - type: string - format: date-time - nullable: true - privateLocation: - type: object - properties: - id: - type: string - format: uuid - name: - type: string - status: - type: string - type: - type: string - required: - - id - - name - - testTargetId - - type - CreateTestTargetBody: - type: object - properties: - testTarget: - type: object - properties: - app: - type: string - description: The app name or project name of the test target - discoveryUrl: - type: string - format: uri - description: The discovery URL of the test target. This is the URL that a discovery tool can use to discover the test target. - required: - - app - - discoveryUrl - TestTargetsResponse: - type: array - items: - $ref: '#/components/schemas/TestTargetResponse' - TestTargetResponse: - type: object - properties: - id: - type: string - format: uuid - app: - type: string - description: The app name or project name of the test target - tags: - type: array - items: - type: string - nullable: true - environments: - type: array - items: - $ref: '#/components/schemas/EnvironmentSimpleResponse' - description: The environments of the test target - required: - - id - - app - TestTargetUpdateRequest: - type: object - properties: - app: - type: string - nullable: true - description: The app name or project name of the test target - testIdAttribute: - type: string - nullable: true - description: The attribute name of the test ID - example: test-automation-id - testRailIntegration: - type: object - properties: - domain: - type: string - description: The domain of the TestRail instance - example: https://mycompany.testrail.io - username: - type: string - description: The username for the TestRail instance - example: user - projectId: - type: string - description: The project ID for the TestRail instance - example: '123' - apiKey: - type: string - description: The TestRail API key for the TestRail instance - example: '123123123' - nullable: true - timeoutPerStep: - type: number - format: int32 - minimum: 5000 - maximum: 30000 - description: The timeout per step in milliseconds - ZodResponse: - type: array - items: - type: object - properties: - code: - type: string - example: invalid_type - description: What error code happened while parsing the request - expected: - type: string - description: What the expected type was - example: object - received: - type: string - description: What the actual passed type was - example: string - path: - type: array - items: - type: string - description: The json path to the wrong parameter - example: key - message: - type: string - description: Human-readable message of the error that occurred while parsing. - example: Expected object, received string - ExternalBatchGenerationBody: - type: object - properties: - prompt: - type: string - description: Prompt to generate the test cases in this batch generation - imageUrls: - type: array - items: - type: string - description: Image URLs to generate the test cases in this batch generation - entryPointUrlPath: - type: string - nullable: true - description: Entry point URL path, where the batch generation will start - environmentId: - type: string - nullable: true - description: Environment ID, where the batch generation will be executed - prerequisiteId: - type: string - nullable: true - description: Prerequisite ID, which will be executed before the batch generation - baseUrl: - type: string - nullable: true - description: Base URL, where the batch generation will be executed - context: - $ref: '#/components/schemas/ExecutionContext' - BatchGenerationResponse: - type: object - properties: - batchGenerationId: - type: string - format: uuid - description: Unique identifier for the batch generation - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - Notification: - type: object - properties: - id: - type: string - format: uuid - description: Unique identifier for the event. - example: 826c15af-644b-4b28-89b4-f50ff34e46b7 - testTargetId: - type: string - format: uuid - description: The unique identifier of the test target this event belongs to. - example: 3435918b-3d29-4ebd-8c68-9a540532f45a - createdAt: - type: string - format: date-time - description: The timestamp when the event was created. - example: '2024-09-06T13:01:51.686Z' - updatedAt: - type: string - format: date-time - description: The timestamp when the event was last updated. - example: '2024-09-06T13:01:51.686Z' - payload: - type: object - description: JSON payload containing event-specific data. - example: - testCaseId: 5deeef76-f37a-418b-8101-7d69529fa561 - type: - type: string - enum: - - VALIDATION_PASSED - - VALIDATION_FAILED - - DISCOVERY_FINISHED - - PROPOSAL_SUCCESS - - PROPOSAL_FAILED - - REPORT_EXECUTION_FINISHED - description: The type of event that occurred. - example: VALIDATION_PASSED - ack: - type: string - enum: - - IN_WEB_APP - description: Optional acknowledgment status of the event. - example: IN_WEB_APP - nullable: true - required: - - id - - testTargetId - - createdAt - - updatedAt - - payload - - type - ExternalDiscoveryBody: - type: object - properties: - name: - type: string - description: Name of the discovered test case - example: Login Form Validation Test - entryPointUrlPath: - type: string - description: Entry point URL path of the discovered test case - example: /login - prerequisiteName: - type: string - description: Prerequisite test case name - example: Cookie Banner Acceptance - externalId: - type: string - description: External ID of the discovered test case - example: ext-test-001 - tagNames: - type: array - items: - type: string - description: Tags to assign to the discovered test case - example: - - authentication - - forms - - validation - prompt: - type: string - description: Prompt to generate the discovered test case - example: Test the login form with valid and invalid credentials - folderName: - type: string - description: Folder name of the discovered test case - example: Authentication Tests - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - description: Type of the discovered test case - example: LOGIN - required: - - name - - prompt - DiscoveryResponse: - type: object - required: - - discoveryId - - testCaseId - properties: - discoveryId: - type: string - format: uuid - description: The ID of the created discovery - testCaseId: - type: string - format: uuid - description: The ID of the associated test case - ExecutionContext: - oneOf: - - type: object - properties: - source: - type: string - enum: - - github - example: github - issueNumber: - type: integer - nullable: true - example: 123 - ref: - type: string - nullable: true - example: refs/heads/main - sha: - type: string - nullable: true - example: abc123def456 - repo: - type: string - example: my-repo - owner: - type: string - example: repo-owner - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - nodeId: - type: string - nullable: true - example: node-123 - - type: object - properties: - source: - type: string - enum: - - azureDevOps - example: azureDevOps - accessToken: - type: string - example: token123 - organization: - type: string - example: my-org - project: - type: string - example: my-project - repositoryId: - type: string - example: repo-123 - sha: - type: string - nullable: true - example: abc123def456 - ref: - type: string - nullable: true - example: refs/heads/main - pullRequestId: - type: integer - nullable: true - example: 101 - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - threadId: - type: string - nullable: true - example: thread-123 - - type: object - properties: - source: - type: string - enum: - - discovery - example: discovery - description: - type: string - example: A discovery test - triggeredBy: - oneOf: - - type: object - properties: - type: - type: string - enum: - - INITIAL - example: INITIAL - - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - manual - example: manual - description: - type: string - example: A manual trigger - triggeredBy: - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - scheduled - example: scheduled - triggeredBy: - type: object - nullable: true - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - - type: object - properties: - source: - type: string - enum: - - proposal - example: proposal - description: - type: string - example: A proposal trigger - triggeredBy: - oneOf: - - type: object - properties: - type: - type: string - enum: - - INITIAL - example: INITIAL - - type: object - properties: - type: - type: string - enum: - - USER - example: USER - userId: - type: string - example: user123 - Variables: - type: object - additionalProperties: - type: array - items: - type: string - description: The variables to overwrite exclusively for this test run. - example: - SPACE_ID: - - 64ee32b4-7365-47a6-b5b0-2903b6ad849d - TestCaseElement: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - interaction: - type: object - nullable: true - properties: - id: - type: string - format: uuid - action: - type: string - enum: - - EXTRACT - - ENTER_TEXT - - CLICK - - SELECT_OPTION - - TYPE_TEXT - - KEY_PRESS - - HOVER - - UPLOAD - - GO_TO - - DRAG_AND_DROP - - CLOSE_PAGE - - OPEN_EMAIL - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - assertion: - type: object - nullable: true - properties: - id: - type: string - format: uuid - expectation: - type: string - enum: - - VISIBLE - - NOT_VISIBLE - - TO_BE_CHECKED - - NOT_TO_BE_CHECKED - - TO_HAVE_VALUE - - TO_CONTAIN_TEXT - - TO_HAVE_STYLE - calledWith: - type: string - nullable: true - testCaseElementId: - type: string - format: uuid - selectors: - type: array - items: - type: object - properties: - id: - type: string - format: uuid - index: - type: integer - selector: - type: string - selectorType: - type: string - enum: - - TEXT - - LABEL - - PLACEHOLDER - - ROLE - options: - type: object - nullable: true - testCaseElementId: - type: string - format: uuid - testCaseId: - type: string - format: uuid - ignoreFailure: - type: boolean - TestTargetSyncSchema: - title: TestTargetSyncSchema - description: schema for import and export of test cases - type: object - properties: - testCases: - type: array - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false - required: - - testCases - additionalProperties: false - TestTargetCodeSchema: - title: TestTargetCodeSchema - description: schema for export of test cases as code - type: object - properties: - executionUrl: - type: string - format: uri - environmentId: - type: string - format: uuid - testCases: - type: array - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false - required: - - executionUrl - - testCases - additionalProperties: false - items: - type: object - properties: - version: - type: string - enum: - - '1' - id: - type: string - format: uuid - type: - type: string - enum: - - LOGIN - - COOKIE_BANNER - - LINK - - TEARDOWN - tagNames: - type: array - items: - type: string - excludedEnvironmentNames: - type: array - items: - type: string - runStatus: - type: string - enum: - - 'OFF' - - 'ON' - dependencyId: - type: string - format: uuid - teardownId: - type: string - format: uuid - elements: - type: array - items: - type: object - properties: - interaction: - anyOf: - - type: object - properties: - action: - type: string - enum: - - CLICK - calledWith: - type: object - properties: - button: - type: string - enum: - - right - - left - - middle - double: - type: boolean - force: - type: boolean - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - ENTER_TEXT - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SELECT_OPTION - calledWith: - anyOf: - - anyOf: - - anyOf: - - type: string - - type: array - items: - type: string - - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - - type: array - items: - type: object - properties: - value: - type: string - label: - type: string - index: - type: number - additionalProperties: false - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - GO_TO - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - KEY_PRESS - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - HOVER - calledWith: - not: {} - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - UPLOAD - calledWith: - type: string - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DOWNLOAD - calledWith: - anyOf: - - type: string - - {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - DRAG_AND_DROP - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - TYPE_TEXT - calledWith: - type: object - properties: - text: - type: string - delay: - type: number - required: - - text - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - EXTRACT - calledWith: - anyOf: - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - TEXT - required: - - variableName - - type - additionalProperties: false - - type: object - properties: - variableName: - type: string - type: - type: string - enum: - - ATTRIBUTE - attributeName: - type: string - required: - - variableName - - type - - attributeName - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - JAVASCRIPT - calledWith: - type: object - properties: - code: - type: string - execType: - type: string - enum: - - browser - - sandbox - default: sandbox - required: - - code - additionalProperties: false - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - OPEN_EMAIL - calledWith: - type: object - properties: - subjectContaining: - type: string - nullable: true - subAddress: - type: string - pattern: ^[^ @]*$ - nullable: true - required: - - subjectContaining - - subAddress - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - CLOSE_PAGE - calledWith: - enum: - - 'null' - nullable: true - required: - - action - - calledWith - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - WAIT_FOR - calledWith: - anyOf: - - type: object - properties: - type: - type: string - enum: - - load - - domContentLoaded - - networkIdle - - nuxtHydration - - nuxtDelayHydration - - webComponentsHydration - - preactHydration - - qwikHydration - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - fixedTime - timeMilliseconds: - type: number - required: - - type - additionalProperties: false - - type: object - properties: - type: - type: string - enum: - - code - code: - type: string - required: - - type - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - - type: object - properties: - action: - type: string - enum: - - SCROLL - calledWith: - type: object - properties: - x: - type: number - 'y': - type: number - additionalProperties: false - nullable: true - required: - - action - additionalProperties: false - assertion: - anyOf: - - type: object - properties: - expectation: - type: string - enum: - - VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_VISIBLE - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - RESPONSE_OK - calledWith: - type: string - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - DISABLED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_VALUE - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - NOT_TO_BE_CHECKED - calledWith: - not: {} - nullable: true - required: - - expectation - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_CONTAIN_TEXT - calledWith: - anyOf: - - type: string - - {} - required: - - expectation - - calledWith - additionalProperties: false - - type: object - properties: - expectation: - type: string - enum: - - TO_HAVE_STYLE - calledWith: - type: array - minItems: 2 - maxItems: 2 - prefixItems: - - type: string - - anyOf: - - type: string - - {} - items: {} - required: - - expectation - - calledWith - additionalProperties: false - ignoreFailure: - type: boolean - selectors: - type: array - items: - type: object - properties: - selectorType: - type: string - enum: - - FRAME - - CSS - - TEXT - - ROLE - - ALT_TEXT - - LABEL - - TEST_ID - - TITLE - - PLACEHOLDER - - FILTER - - FIRST - - LAST - - NTH - selector: - anyOf: - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - enum: - - alert - - alertdialog - - application - - article - - banner - - blockquote - - button - - caption - - cell - - checkbox - - code - - columnheader - - combobox - - complementary - - contentinfo - - definition - - deletion - - dialog - - directory - - document - - emphasis - - feed - - figure - - form - - generic - - grid - - gridcell - - group - - heading - - img - - insertion - - link - - list - - listbox - - listitem - - log - - main - - marquee - - math - - meter - - menu - - menubar - - menuitem - - menuitemcheckbox - - menuitemradio - - navigation - - none - - note - - option - - paragraph - - presentation - - progressbar - - radio - - radiogroup - - region - - row - - rowgroup - - rowheader - - scrollbar - - search - - searchbox - - separator - - slider - - spinbutton - - status - - strong - - subscript - - superscript - - switch - - tab - - table - - tablist - - tabpanel - - term - - textbox - - time - - timer - - toolbar - - tooltip - - tree - - treegrid - - treeitem - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - anyOf: - - type: string - - {} - - type: string - - type: string - - type: string - nullable: true - options: - type: object - properties: - name: - anyOf: - - type: string - - {} - exact: - type: boolean - checked: - type: boolean - disabled: - type: boolean - expanded: - type: boolean - includeHidden: - type: boolean - level: - type: number - pressed: - type: boolean - selected: - type: boolean - hasText: - anyOf: - - type: string - - {} - hasNotText: - anyOf: - - type: string - - {} - additionalProperties: false - nullable: true - required: - - selectorType - additionalProperties: false - required: - - selectors - additionalProperties: false - description: - type: string - entryPointUrlPath: - type: string - folderName: - type: string - externalId: - type: string - prompt: - type: string - localEditingStatus: - type: string - enum: - - IN_PROGRESS - - DONE - - CANCELLED - required: - - version - - id - - runStatus - - elements - - description - - prompt - additionalProperties: false From ff12e1cece0654d71c94e51fbaeae05806611ad2 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 16:25:18 +0100 Subject: [PATCH 33/36] undo debuggin apigen command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 24bece3..a3e792d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "dev:local": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.local.json OCTOMIND_API_URL=http://localhost:3000/api tsx src/index.ts", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.prod.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", - "apigen": "openapi-typescript openapi.yaml --output src/api.ts && orval", + "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", "build": "pnpm apigen && npx genversion -des src/version.ts && pnpm gendoc && tsc --project tsconfig.build.json", "octomind": "pnpm apigen && tsx src/index.ts", "test": "pnpm apigen && npx genversion -des src/version.ts && vitest", From 0404272ef24387de336d6c2bb7030da36aa5132f Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 16:25:53 +0100 Subject: [PATCH 34/36] fix formatting --- package.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index a3e792d..0b33e1c 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,7 @@ "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", - "files": [ - "dist", - "src", - "README.md", - "LICENSE", - "package.json" - ], + "files": ["dist", "src", "README.md", "LICENSE", "package.json"], "engines": { "node": ">=22.0.0" }, From ba9bdbd8e71e4d6886c832437a4f8ce1df9add3d Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 17:10:01 +0100 Subject: [PATCH 35/36] fix tests --- src/tools/yamlMutations/edit.ts | 14 +++++++++----- tests/tools/yamlMutations/edit.spec.ts | 8 ++++---- vitest.config.ts | 5 +++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index ac1c9f8..48bffe8 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -100,18 +100,22 @@ export const edit = async (options: EditOptions): Promise => { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const { versionId, testResultId } = + const syncData = response.syncDataByStableId[testCaseToEdit.id]; - if (!versionId) { + if (!syncData) { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } + const { versionId, testResultId } = syncData; + const parsedBaseUrl = URL.parse(BASE_URL); - let localEditingUrl = `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit?detailsPanelRail=steps&testTargetId=${options.testTargetId}&testCaseId=${versionId}`; + const localEditingUrl = new URL( + `${parsedBaseUrl?.protocol}//${parsedBaseUrl?.host}/testtargets/${options.testTargetId}/testcases/${versionId}/localEdit`, + ); if (testResultId) { - localEditingUrl += `&testResultId=${testResultId}`; + localEditingUrl.searchParams.set("testResultId", testResultId); } - await open(localEditingUrl); + await open(localEditingUrl.href); console.log( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index b003621..f1772dd 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -97,7 +97,7 @@ describe("edit", () => { vi.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [], - versionIdByStableId: {}, + syncDataByStableId: {}, }); await expect( @@ -117,7 +117,7 @@ describe("edit", () => { vi.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [], - versionIdByStableId: { "test-id": "version-123" }, + syncDataByStableId: { "test-id": { versionId: "version-123" } }, }); vi.mocked(mockedClient.GET) .mockResolvedValueOnce({ @@ -158,7 +158,7 @@ describe("edit", () => { vi.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [], - versionIdByStableId: { "child-id": "version-123" }, + syncDataByStableId: { "child-id": { versionId: "version-123" } }, }); vi.mocked(mockedClient.GET).mockResolvedValue({ data: { localEditingStatus: "CANCELLED" }, @@ -209,7 +209,7 @@ describe("edit", () => { vi.mocked(draftPush).mockResolvedValue({ success: true, versionIds: [], - versionIdByStableId: { "test-id": "version-123" }, + syncDataByStableId: { "test-id": { versionId: "version-123" } }, }); vi.mocked(mockedClient.GET) .mockResolvedValueOnce({ diff --git a/vitest.config.ts b/vitest.config.ts index 8c1ac4c..d3ba980 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ test: { mockReset: true, globals: true, - environment: 'node' - } + environment: 'node', + include: ["tests/**/*.spec.ts"], + }, }) From c93219c7c92f583ac7602d00f40cbe94a91f89c0 Mon Sep 17 00:00:00 2001 From: Fabian Both Date: Wed, 14 Jan 2026 17:14:42 +0100 Subject: [PATCH 36/36] add tests --- src/tools/yamlMutations/edit.ts | 3 +-- tests/tools/yamlMutations/edit.spec.ts | 36 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 48bffe8..05f4e52 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -100,8 +100,7 @@ export const edit = async (options: EditOptions): Promise => { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } - const syncData = - response.syncDataByStableId[testCaseToEdit.id]; + const syncData = response.syncDataByStableId[testCaseToEdit.id]; if (!syncData) { throw new Error(`Could not edit test case with id '${testCaseToEdit.id}'`); } diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index f1772dd..5ba2301 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -1,5 +1,6 @@ import fs from "fs"; +import open from "open"; import ora from "ora"; import { beforeEach, describe, expect, it, MockedObject, vi } from "vitest"; import { mock } from "vitest-mock-extended"; @@ -227,4 +228,39 @@ describe("edit", () => { expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); }); + + it.each([ + { + testResultId: "result-456", + expectedUrl: expect.stringContaining("testResultId=result-456"), + }, + { + testResultId: undefined, + expectedUrl: expect.not.stringContaining("testResultId"), + }, + ])("handles testResultId=$testResultId in URL correctly", async ({ + testResultId, + expectedUrl, + }) => { + const testCase = createMockSyncTestCase({ id: "test-id" }); + + vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); + vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( + "/mock/.octomind/test.yaml", + ); + vi.mocked(fs.readFileSync).mockReturnValue(yaml.stringify(testCase)); + vi.mocked(readTestCasesFromDir).mockReturnValue([testCase]); + vi.mocked(draftPush).mockResolvedValue({ + success: true, + versionIds: [], + syncDataByStableId: { + "test-id": { versionId: "version-123", testResultId }, + }, + }); + vi.mocked(waitForLocalChangesToBeFinished).mockResolvedValue("cancelled"); + + await edit({ testTargetId: "someId", filePath: "test.yaml" }); + + expect(open).toHaveBeenCalledWith(expectedUrl); + }); });