diff --git a/.c8rc.json b/.c8rc.json index 88b1a167..b998502a 100644 --- a/.c8rc.json +++ b/.c8rc.json @@ -1,7 +1,7 @@ { "all": true, "include": ["dist/**/*.js"], - "exclude": ["dist/types/generated/**/*.js", "dist/**/*.d.ts"], + "exclude": ["dist/types/generated/**/*.js", "dist/**/*.d.ts", "dist/**/*.cjs"], "reporter": ["text", "lcov", "json", "json-summary"], "report-dir": "coverage", "check-coverage": false diff --git a/.gitignore b/.gitignore index 067b9d5b..5bbb804b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +settings.local.json + # Logs logs *.log diff --git a/dist/detectTests.d.ts b/dist/detectTests.d.ts new file mode 100644 index 00000000..d0cf7e34 --- /dev/null +++ b/dist/detectTests.d.ts @@ -0,0 +1,106 @@ +/** + * Browser-compatible test detection utilities. + * This module provides pure parsing functionality that works with strings/objects, + * without dependencies on Node.js file system or path modules. + */ +export interface FileType { + name?: string; + extensions: string[]; + inlineStatements?: { + testStart?: string[]; + testEnd?: string[]; + ignoreStart?: string[]; + ignoreEnd?: string[]; + step?: string[]; + }; + markup?: Array<{ + regex: string[]; + actions?: (string | Record)[]; + batchMatches?: boolean; + }>; + runShell?: Record; +} +export interface DetectTestsConfig { + detectSteps?: boolean; + origin?: string; + logLevel?: string; + _herettoPathMapping?: Record; +} +export interface DetectedTest { + testId?: string; + detectSteps?: boolean; + steps: Array>; + [key: string]: any; +} +export interface DetectTestsInput { + content: string; + filePath: string; + fileType: FileType; + config?: DetectTestsConfig; +} +/** + * Browser-compatible test detection function. + * Detects tests from content string using specified file type configuration. + * + * This is the main entry point for test detection in Common. + * It works with content strings rather than file paths, making it browser-compatible. + * + * @param input - Detection input + * @param input.content - Content string to parse for tests + * @param input.filePath - File path (for metadata only, not file I/O) + * @param input.fileType - File type configuration with parsing rules + * @param input.config - Optional configuration + * @returns Array of detected tests + * + * @example + * ```typescript + * const tests = await detectTests({ + * content: markdownContent, + * filePath: 'docs/test.md', + * fileType: { extensions: ['md'], markup: [...] }, + * config: { detectSteps: true } + * }); + * ``` + */ +export declare function detectTests(input: DetectTestsInput): Promise; +/** + * Parses XML-style attributes to an object. + * Example: 'wait=500' becomes { wait: 500 } + * Example: 'testId="myTestId" detectSteps=false' becomes { testId: "myTestId", detectSteps: false } + * Example: 'httpRequest.url="https://example.com"' becomes { httpRequest: { url: "https://example.com" } } + */ +export declare function parseXmlAttributes({ stringifiedObject }: { + stringifiedObject: string; +}): Record | null; +/** + * Parses a JSON or YAML object from a string. + */ +export declare function parseObject({ stringifiedObject }: { + stringifiedObject: string; +}): Record | null; +/** + * Replaces numeric variables ($0, $1, etc.) in strings and objects with provided values. + */ +export declare function replaceNumericVariables(stringOrObjectSource: string | Record, values: Record): string | Record | null; +/** + * Parses raw test content into an array of structured test objects. + * This is a browser-compatible function that works with strings and doesn't require file system access. + * + * @param options - Options for parsing + * @param options.config - Test configuration object + * @param options.content - Raw file content as a string + * @param options.filePath - Path to the file being parsed (for metadata, not file I/O) + * @param options.fileType - File type definition containing parsing rules + * @returns Array of parsed and validated test objects + */ +export declare function parseContent({ config, content, filePath, fileType, }: { + config: DetectTestsConfig; + content: string; + filePath: string; + fileType: FileType; +}): Promise; +/** + * Simple browser-compatible logging function. + */ +export declare function log(config: DetectTestsConfig, level: string, message: any): void; +//# sourceMappingURL=detectTests.d.ts.map \ No newline at end of file diff --git a/dist/detectTests.d.ts.map b/dist/detectTests.d.ts.map new file mode 100644 index 00000000..f937f504 --- /dev/null +++ b/dist/detectTests.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"detectTests.d.ts","sourceRoot":"","sources":["../src/detectTests.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,CAAC,EAAE;QACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CA6DnH;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,iBAAiB,EAAE,EAAE;IAAE,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAkD5G;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,oBAAoB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CA2DrC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;CACpB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA+R1B;AAsBD;;GAEG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAqBhF"} \ No newline at end of file diff --git a/dist/detectTests.js b/dist/detectTests.js new file mode 100644 index 00000000..8cdfc2ec --- /dev/null +++ b/dist/detectTests.js @@ -0,0 +1,562 @@ +/** + * Browser-compatible test detection utilities. + * This module provides pure parsing functionality that works with strings/objects, + * without dependencies on Node.js file system or path modules. + */ +import YAML from "yaml"; +import { validate, transformToSchemaKey } from "./validate.js"; +// Web Crypto API compatible UUID generation +/* c8 ignore next 10 - crypto.randomUUID always available in Node.js; fallback is for browsers */ +function generateUUID() { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} +/** + * Browser-compatible test detection function. + * Detects tests from content string using specified file type configuration. + * + * This is the main entry point for test detection in Common. + * It works with content strings rather than file paths, making it browser-compatible. + * + * @param input - Detection input + * @param input.content - Content string to parse for tests + * @param input.filePath - File path (for metadata only, not file I/O) + * @param input.fileType - File type configuration with parsing rules + * @param input.config - Optional configuration + * @returns Array of detected tests + * + * @example + * ```typescript + * const tests = await detectTests({ + * content: markdownContent, + * filePath: 'docs/test.md', + * fileType: { extensions: ['md'], markup: [...] }, + * config: { detectSteps: true } + * }); + * ``` + */ +export async function detectTests(input) { + return parseContent({ + config: input.config || {}, + content: input.content, + filePath: input.filePath, + fileType: input.fileType, + }); +} +/** + * Parses XML-style attributes to an object. + * Example: 'wait=500' becomes { wait: 500 } + * Example: 'testId="myTestId" detectSteps=false' becomes { testId: "myTestId", detectSteps: false } + * Example: 'httpRequest.url="https://example.com"' becomes { httpRequest: { url: "https://example.com" } } + */ +export function parseXmlAttributes({ stringifiedObject }) { + if (typeof stringifiedObject !== "string") { + return null; + } + const str = stringifiedObject.trim(); + // Check if it looks like JSON or YAML - if so, return null to let JSON/YAML parsers handle it + if (str.startsWith("{") || str.startsWith("[")) { + return null; + } + // Check if it looks like YAML (key: value pattern) + const yamlPattern = /^\w+:\s/; + if (yamlPattern.test(str)) { + return null; + } + if (str.startsWith("-")) { + return null; + } + // Parse XML-style attributes + const result = {}; + const attrRegex = /([\w.]+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g; + let match; + let hasMatches = false; + while ((match = attrRegex.exec(str)) !== null) { + hasMatches = true; + const keyPath = match[1]; + let value = match[2] !== undefined ? match[2] : match[3] !== undefined ? match[3] : match[4]; + // Try to parse as boolean + if (value === "true") { + value = true; + } + else if (value === "false") { + value = false; + } + else if (!isNaN(value) && value !== "") { + value = Number(value); + } + // Handle dot notation for nested objects + if (keyPath.includes(".")) { + const keys = keyPath.split("."); + let current = result; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key] || typeof current[key] !== "object") { + current[key] = {}; + } + current = current[key]; + } + current[keys[keys.length - 1]] = value; + } + else { + result[keyPath] = value; + } + } + return hasMatches ? result : null; +} +/** + * Parses a JSON or YAML object from a string. + */ +export function parseObject({ stringifiedObject }) { + if (typeof stringifiedObject === "string") { + // First, try to parse as XML attributes + const xmlAttrs = parseXmlAttributes({ stringifiedObject }); + if (xmlAttrs !== null) { + return xmlAttrs; + } + // Try to parse as JSON + try { + const json = JSON.parse(stringifiedObject); + if (typeof json !== "object" || json === null || Array.isArray(json)) + return null; + return json; + } + catch (jsonError) { + // JSON parsing failed - check if this looks like escaped JSON + const trimmedString = stringifiedObject.trim(); + const looksLikeEscapedJson = (trimmedString.startsWith("{") || trimmedString.startsWith("[")) && + trimmedString.includes('\\"'); + if (looksLikeEscapedJson) { + try { + const stringToParse = JSON.parse('"' + stringifiedObject + '"'); + const result = JSON.parse(stringToParse); + if (typeof result !== "object" || result === null || Array.isArray(result)) + return null; + return result; + } + catch { + // Fallback to simple quote replacement + try { + const unescaped = stringifiedObject.replace(/\\"/g, '"'); + const result = JSON.parse(unescaped); + if (typeof result !== "object" || result === null || Array.isArray(result)) + return null; + return result; + } + catch { + // Continue to YAML parsing + } + } + } + // Try to parse as YAML + try { + const yaml = YAML.parse(stringifiedObject); + if (typeof yaml !== "object" || yaml === null || Array.isArray(yaml)) + return null; + return yaml; + } + catch (yamlError) { + return null; + } + } + } + return stringifiedObject; +} +/** + * Replaces numeric variables ($0, $1, etc.) in strings and objects with provided values. + */ +export function replaceNumericVariables(stringOrObjectSource, values) { + let stringOrObject = JSON.parse(JSON.stringify(stringOrObjectSource)); + if (typeof stringOrObject !== "string" && typeof stringOrObject !== "object") { + throw new Error("Invalid stringOrObject type"); + } + if (typeof values !== "object") { + throw new Error("Invalid values type"); + } + if (typeof stringOrObject === "string") { + const matches = stringOrObject.match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + return null; + } + else { + stringOrObject = stringOrObject.replace(/\$[0-9]+/g, (variable) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + if (typeof stringOrObject === "object") { + Object.keys(stringOrObject).forEach((key) => { + if (typeof stringOrObject[key] === "object") { + const result = replaceNumericVariables(stringOrObject[key], values); + /* c8 ignore next 3 - defensive guard: recursive calls on objects can't return null currently */ + if (result === null) { + delete stringOrObject[key]; + } + else { + stringOrObject[key] = result; + } + } + else if (typeof stringOrObject[key] === "string") { + const matches = stringOrObject[key].match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + delete stringOrObject[key]; + } + else { + stringOrObject[key] = stringOrObject[key].replace(/\$[0-9]+/g, (variable) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + }); + } + return stringOrObject; +} +/** + * Parses raw test content into an array of structured test objects. + * This is a browser-compatible function that works with strings and doesn't require file system access. + * + * @param options - Options for parsing + * @param options.config - Test configuration object + * @param options.content - Raw file content as a string + * @param options.filePath - Path to the file being parsed (for metadata, not file I/O) + * @param options.fileType - File type definition containing parsing rules + * @returns Array of parsed and validated test objects + */ +export async function parseContent({ config, content, filePath, fileType, }) { + const statements = []; + const statementTypes = ["testStart", "testEnd", "ignoreStart", "ignoreEnd", "step"]; + function findTest({ tests, testId }) { + let test = tests.find((t) => t.testId === testId); + if (!test) { + test = { testId, steps: [] }; + tests.push(test); + } + return test; + } + // Test for each statement type + statementTypes.forEach((statementType) => { + if (typeof fileType.inlineStatements === "undefined" || + typeof fileType.inlineStatements[statementType] === "undefined") + return; + fileType.inlineStatements[statementType].forEach((statementRegex) => { + let regex; + try { + regex = new RegExp(statementRegex, "g"); + } + catch { + return; + } + const matches = [...content.matchAll(regex)]; + matches.forEach((match) => { + match.type = statementType; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + }); + }); + if (config.detectSteps && fileType.markup) { + fileType.markup.forEach((markup) => { + markup.regex.forEach((pattern) => { + let regex; + try { + regex = new RegExp(pattern, "g"); + } + catch { + return; + } + const matches = [...content.matchAll(regex)]; + if (matches.length > 0 && markup.batchMatches) { + const combinedMatch = { + 1: matches.map((match) => match[1] || match[0]).join("\n"), + type: "detectedStep", + markup: markup, + sortIndex: Math.min(...matches.map((match) => match.index)), + }; + statements.push(combinedMatch); + } + else if (matches.length > 0) { + matches.forEach((match) => { + match.type = "detectedStep"; + match.markup = markup; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + } + }); + }); + } + // Sort statements by index + statements.sort((a, b) => a.sortIndex - b.sortIndex); + // Process statements into tests and steps + let tests = []; + let testId = generateUUID(); + let ignore = false; + statements.forEach((statement) => { + let test; + let statementContent = ""; + let stepsCleanup = false; + switch (statement.type) { + case "testStart": { + statementContent = statement[1] || statement[0]; + const parsedTest = parseObject({ stringifiedObject: statementContent }); + if (!parsedTest || typeof parsedTest !== 'object') + break; + test = parsedTest; + // If v2 schema, convert to v3 + if (test.id || test.file || test.setup || test.cleanup) { + if (!test.steps) { + test.steps = [{ action: "goTo", url: "https://doc-detective.com" }]; + stepsCleanup = true; + } + const transformed = transformToSchemaKey({ + object: test, + currentSchema: "test_v2", + targetSchema: "test_v3", + }); + test = transformed; + if (stepsCleanup && test) { + test.steps = []; + } + } + if (test.testId) { + testId = test.testId; + } + else { + test.testId = testId; + } + if (test.detectSteps === "false") { + test.detectSteps = false; + } + else if (test.detectSteps === "true") { + test.detectSteps = true; + } + if (!test.steps) { + test.steps = []; + } + tests.push(test); + break; + } + case "testEnd": + testId = generateUUID(); + ignore = false; + break; + case "ignoreStart": + ignore = true; + break; + case "ignoreEnd": + ignore = false; + break; + case "detectedStep": + if (ignore) + break; + test = findTest({ tests, testId }); + if (typeof test.detectSteps !== "undefined" && !test.detectSteps) { + break; + } + if (statement?.markup?.actions) { + statement.markup.actions.forEach((action) => { + let step = {}; + if (typeof action === "string") { + if (action === "runCode") + return; + step[action] = statement[1] || statement[0]; + if (config.origin && (action === "goTo" || action === "checkLink")) { + step[action] = { ...step[action], origin: config.origin }; + } + // Attach sourceIntegration for Heretto + if (action === "screenshot" && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + const screenshotPath = step[action]; + step[action] = { + path: screenshotPath, + sourceIntegration: { + type: "heretto", + integrationName: herettoIntegration, + filePath: screenshotPath, + contentPath: filePath, + }, + }; + } + } + } + else { + const replacedStep = replaceNumericVariables(action, statement); + /* c8 ignore next - typeof string check is defensive; object actions always return objects */ + if (!replacedStep || typeof replacedStep === 'string') + return; + step = replacedStep; + // Attach sourceIntegration for Heretto + if (step.screenshot && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + if (typeof step.screenshot === "string") { + step.screenshot = { path: step.screenshot }; + } + else if (typeof step.screenshot === "boolean") { + step.screenshot = {}; + } + step.screenshot.sourceIntegration = { + type: "heretto", + integrationName: herettoIntegration, + filePath: step.screenshot.path || "", + contentPath: filePath, + }; + } + } + } + // Normalize step field formats + if (step.httpRequest?.request) { + if (typeof step.httpRequest.request.headers === "string") { + try { + const headers = {}; + step.httpRequest.request.headers.split("\n").forEach((header) => { + const colonIndex = header.indexOf(":"); + if (colonIndex === -1) + return; + const key = header.substring(0, colonIndex).trim(); + const value = header.substring(colonIndex + 1).trim(); + /* c8 ignore next 3 - V8 phantom branch in && short-circuit */ + if (key && value) { + headers[key] = value; + } + }); + step.httpRequest.request.headers = headers; + /* c8 ignore next 2 - string split/forEach can't throw */ + } + catch (error) { + } + } + if (typeof step.httpRequest.request.body === "string" && + (step.httpRequest.request.body.trim().startsWith("{") || + step.httpRequest.request.body.trim().startsWith("["))) { + try { + step.httpRequest.request.body = JSON.parse(step.httpRequest.request.body); + } + catch (error) { + // Ignore parsing errors + } + } + } + // Validate step + const valid = validate({ + schemaKey: "step_v3", + object: step, + addDefaults: false, + }); + if (!valid.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = valid.object; + test.steps.push(step); + }); + } + break; + case "step": { + if (ignore) + break; + test = findTest({ tests, testId }); + statementContent = statement[1] || statement[0]; + const parsedStep = parseObject({ stringifiedObject: statementContent }); + if (!parsedStep || typeof parsedStep !== 'object') + break; + let step = parsedStep; + const validation = validate({ + schemaKey: "step_v3", + object: step, + addDefaults: false, + }); + /* c8 ignore start - V8 phantom branch on if-else/switch-case */ + if (!validation.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = validation.object; + test.steps.push(step); + break; + /* c8 ignore stop */ + } + /* c8 ignore next 2 - all statement types are handled above */ + default: + break; + } + }); + // Validate test objects + const validatedTests = []; + tests.forEach((test) => { + const validation = validate({ + schemaKey: "test_v3", + object: test, + addDefaults: false, + }); + if (!validation.valid) { + log(config, "warn", `Couldn't convert test in ${filePath} to valid test. Skipping.`); + return; + } + validatedTests.push(validation.object); + }); + return validatedTests; +} +/** + * Helper function to find which Heretto integration a file belongs to. + */ +function findHerettoIntegration(config, filePath) { + /* c8 ignore next - callers always check _herettoPathMapping before calling */ + if (!config._herettoPathMapping) + return null; + // Simple string matching since we don't have path.resolve in browser + const normalizedFilePath = filePath.replace(/\\/g, "/"); + for (const [outputPath, integrationName] of Object.entries(config._herettoPathMapping)) { + const normalizedOutputPath = outputPath.replace(/\\/g, "/"); + if (normalizedFilePath.startsWith(normalizedOutputPath)) { + return integrationName; + } + } + return null; +} +/** + * Simple browser-compatible logging function. + */ +export function log(config, level, message) { + const logLevels = ["silent", "error", "warn", "info", "debug"]; + // Normalize 'warning' to 'warn' for both config and message levels + const configLevel = (config.logLevel || "info") === "warning" ? "warn" : (config.logLevel || "info"); + const normalizedLevel = level === "warning" ? "warn" : level; + const configLevelIndex = logLevels.indexOf(configLevel); + const messageLevelIndex = logLevels.indexOf(normalizedLevel); + if (configLevelIndex < 0 || messageLevelIndex < 0) + return; + if (messageLevelIndex > configLevelIndex) + return; + // Treat message-level 'silent' as a no-op to avoid calling an undefined console method + if (normalizedLevel === "silent") + return; + if (typeof message === "object") { + console[normalizedLevel](JSON.stringify(message, null, 2)); + } + else { + console[normalizedLevel](message); + } +} +//# sourceMappingURL=detectTests.js.map \ No newline at end of file diff --git a/dist/detectTests.js.map b/dist/detectTests.js.map new file mode 100644 index 00000000..30d08e36 --- /dev/null +++ b/dist/detectTests.js.map @@ -0,0 +1 @@ +{"version":3,"file":"detectTests.js","sourceRoot":"","sources":["../src/detectTests.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAG/D,4CAA4C;AAC5C,iGAAiG;AACjG,SAAS,YAAY;IACnB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,UAAS,CAAC;QACvE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAyCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAuB;IACvD,OAAO,YAAY,CAAC;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAE,iBAAiB,EAAiC;IACrF,IAAI,OAAO,iBAAiB,KAAK,QAAQ,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAErC,8FAA8F;IAC9F,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mDAAmD;IACnD,MAAM,WAAW,GAAG,SAAS,CAAC;IAC9B,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6BAA6B;IAC7B,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,yCAAyC,CAAC;IAC5D,IAAI,KAAK,CAAC;IACV,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,UAAU,GAAG,IAAI,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,KAAK,GAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAElG,0BAA0B;QAC1B,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YACrB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;aAAM,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAC7B,KAAK,GAAG,KAAK,CAAC;QAChB,CAAC;aAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACzC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QAED,yCAAyC;QACzC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,OAAO,GAAG,MAAM,CAAC;YAErB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;oBACtD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;gBACpB,CAAC;gBACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;YAED,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,iBAAiB,EAAiC;IAC9E,IAAI,OAAO,iBAAiB,KAAK,QAAQ,EAAE,CAAC;QAC1C,wCAAwC;QACxC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC3D,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,uBAAuB;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClF,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,8DAA8D;YAC9D,MAAM,aAAa,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC/C,MAAM,oBAAoB,GACxB,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBAChE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAEhC,IAAI,oBAAoB,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,iBAAiB,GAAG,GAAG,CAAC,CAAC;oBAChE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;oBACzC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;wBAAE,OAAO,IAAI,CAAC;oBACxF,OAAO,MAAM,CAAC;gBAChB,CAAC;gBAAC,MAAM,CAAC;oBACP,uCAAuC;oBACvC,IAAI,CAAC;wBACH,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;wBACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;wBACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;4BAAE,OAAO,IAAI,CAAC;wBACxF,OAAO,MAAM,CAAC;oBAChB,CAAC;oBAAC,MAAM,CAAC;wBACP,2BAA2B;oBAC7B,CAAC;gBACH,CAAC;YACH,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;gBAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAClF,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;gBACnB,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,iBAAwB,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,oBAAkD,EAClD,MAA2B;IAE3B,IAAI,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAEtE,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAClD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBACpC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC;YAC9E,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO,IAAI,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,EAAE;oBAChE,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;oBACpC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvB,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAC1C,IAAI,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC5C,MAAM,MAAM,GAAG,uBAAuB,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;gBACpE,gGAAgG;gBAChG,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;gBAC7B,CAAC;qBAAM,CAAC;oBACN,cAAc,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;gBAC/B,CAAC;YACH,CAAC;iBAAM,IAAI,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnD,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACvD,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,QAAgB,EAAE,EAAE;wBAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;wBACpC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC;oBAC9E,CAAC,CAAC,CAAC;oBACH,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;oBAC7B,CAAC;yBAAM,CAAC;wBACN,cAAc,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAgB,EAAE,EAAE;4BAClF,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;4BACpC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;wBACvB,CAAC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,QAAQ,GAMT;IACC,MAAM,UAAU,GAAe,EAAE,CAAC;IAClC,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAEpF,SAAS,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAA6C;QAC5E,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+BAA+B;IAC/B,cAAc,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,EAAE;QACvC,IACE,OAAO,QAAQ,CAAC,gBAAgB,KAAK,WAAW;YAChD,OAAO,QAAQ,CAAC,gBAAgB,CAAC,aAAuD,CAAC,KAAK,WAAW;YAEzG,OAAO;QAET,QAAQ,CAAC,gBAAgB,CAAC,aAAuD,CAAE,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;YAC7G,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,KAAK,GAAG,IAAI,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7C,OAAO,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,EAAE;gBAC7B,KAAK,CAAC,IAAI,GAAG,aAAa,CAAC;gBAC3B,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;YAC3E,CAAC,CAAC,CAAC;YACH,UAAU,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1C,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YACjC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC/B,IAAI,KAAa,CAAC;gBAClB,IAAI,CAAC;oBACH,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBACnC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO;gBACT,CAAC;gBACD,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC7C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;oBAC9C,MAAM,aAAa,GAAQ;wBACzB,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;wBAC1D,IAAI,EAAE,cAAc;wBACpB,MAAM,EAAE,MAAM;wBACd,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAM,CAAC,CAAC;qBAC7D,CAAC;oBACF,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;gBACjC,CAAC;qBAAM,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,OAAO,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,EAAE;wBAC7B,KAAK,CAAC,IAAI,GAAG,cAAc,CAAC;wBAC5B,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;wBACtB,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;oBAC3E,CAAC,CAAC,CAAC;oBACH,UAAU,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2BAA2B;IAC3B,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAErD,0CAA0C;IAC1C,IAAI,KAAK,GAAmB,EAAE,CAAC;IAC/B,IAAI,MAAM,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,UAAU,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;QAC/B,IAAI,IAA8B,CAAC;QACnC,IAAI,gBAAgB,GAAG,EAAE,CAAC;QAC1B,IAAI,YAAY,GAAG,KAAK,CAAC;QAEzB,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,gBAAgB,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;gBAChD,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACxE,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;oBAAE,MAAM;gBAEzD,IAAI,GAAG,UAA0B,CAAC;gBAElC,8BAA8B;gBAC9B,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACvD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;wBAChB,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,2BAA2B,EAAE,CAAC,CAAC;wBACpE,YAAY,GAAG,IAAI,CAAC;oBACtB,CAAC;oBACD,MAAM,WAAW,GAAG,oBAAoB,CAAC;wBACvC,MAAM,EAAE,IAAI;wBACZ,aAAa,EAAE,SAAsB;wBACrC,YAAY,EAAE,SAAsB;qBACrC,CAAC,CAAC;oBACH,IAAI,GAAG,WAA2B,CAAC;oBACnC,IAAI,YAAY,IAAI,IAAI,EAAE,CAAC;wBACzB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;oBAClB,CAAC;gBACH,CAAC;gBAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;gBACvB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;gBACvB,CAAC;gBAED,IAAI,IAAI,CAAC,WAAW,KAAK,OAAc,EAAE,CAAC;oBACxC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;gBAC3B,CAAC;qBAAM,IAAI,IAAI,CAAC,WAAW,KAAK,MAAa,EAAE,CAAC;oBAC9C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBAC1B,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;oBAChB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;gBAClB,CAAC;gBACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjB,MAAM;YACR,CAAC;YAED,KAAK,SAAS;gBACZ,MAAM,GAAG,YAAY,EAAE,CAAC;gBACxB,MAAM,GAAG,KAAK,CAAC;gBACf,MAAM;YAER,KAAK,aAAa;gBAChB,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YAER,KAAK,WAAW;gBACd,MAAM,GAAG,KAAK,CAAC;gBACf,MAAM;YAER,KAAK,cAAc;gBACjB,IAAI,MAAM;oBAAE,MAAM;gBAClB,IAAI,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;gBACnC,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjE,MAAM;gBACR,CAAC;gBACD,IAAI,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;oBAC/B,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAoC,EAAE,EAAE;wBACxE,IAAI,IAAI,GAAwB,EAAE,CAAC;wBACnC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;4BAC/B,IAAI,MAAM,KAAK,SAAS;gCAAE,OAAO;4BACjC,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;4BAC5C,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,WAAW,CAAC,EAAE,CAAC;gCACnE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;4BAC5D,CAAC;4BACD,uCAAuC;4BACvC,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;gCAC1D,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gCACpE,IAAI,kBAAkB,EAAE,CAAC;oCACvB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;oCACpC,IAAI,CAAC,MAAM,CAAC,GAAG;wCACb,IAAI,EAAE,cAAc;wCACpB,iBAAiB,EAAE;4CACjB,IAAI,EAAE,SAAS;4CACf,eAAe,EAAE,kBAAkB;4CACnC,QAAQ,EAAE,cAAc;4CACxB,WAAW,EAAE,QAAQ;yCACtB;qCACF,CAAC;gCACJ,CAAC;4BACH,CAAC;wBACH,CAAC;6BAAM,CAAC;4BACN,MAAM,YAAY,GAAG,uBAAuB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;4BAChE,6FAA6F;4BAC7F,IAAI,CAAC,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ;gCAAE,OAAO;4BAC9D,IAAI,GAAG,YAAY,CAAC;4BAEpB,uCAAuC;4BACvC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;gCAClD,MAAM,kBAAkB,GAAG,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gCACpE,IAAI,kBAAkB,EAAE,CAAC;oCACvB,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;wCACxC,IAAI,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;oCAC9C,CAAC;yCAAM,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;wCAChD,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;oCACvB,CAAC;oCACD,IAAI,CAAC,UAAU,CAAC,iBAAiB,GAAG;wCAClC,IAAI,EAAE,SAAS;wCACf,eAAe,EAAE,kBAAkB;wCACnC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE;wCACpC,WAAW,EAAE,QAAQ;qCACtB,CAAC;gCACJ,CAAC;4BACH,CAAC;wBACH,CAAC;wBAED,+BAA+B;wBAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;4BAC9B,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gCACzD,IAAI,CAAC;oCACH,MAAM,OAAO,GAA2B,EAAE,CAAC;oCAC3C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,MAAc,EAAE,EAAE;wCACtE,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;wCACvC,IAAI,UAAU,KAAK,CAAC,CAAC;4CAAE,OAAO;wCAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;wCACnD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;wCACtD,8DAA8D;wCAC9D,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;4CACjB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;wCACvB,CAAC;oCACH,CAAC,CAAC,CAAC;oCACH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;oCAC7C,yDAAyD;gCACzD,CAAC;gCAAC,OAAO,KAAK,EAAE,CAAC;gCACjB,CAAC;4BACH,CAAC;4BACD,IACE,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ;gCACjD,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;oCACnD,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EACvD,CAAC;gCACD,IAAI,CAAC;oCACH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gCAC5E,CAAC;gCAAC,OAAO,KAAK,EAAE,CAAC;oCACf,wBAAwB;gCAC1B,CAAC;4BACH,CAAC;wBACH,CAAC;wBAED,gBAAgB;wBAChB,MAAM,KAAK,GAAG,QAAQ,CAAC;4BACrB,SAAS,EAAE,SAAsB;4BACjC,MAAM,EAAE,IAAI;4BACZ,WAAW,EAAE,KAAK;yBACnB,CAAC,CAAC;wBACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;4BACjB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;4BAClF,OAAO;wBACT,CAAC;wBACD,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;wBACpB,IAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACzB,CAAC,CAAC,CAAC;gBACL,CAAC;gBACD,MAAM;YAER,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,IAAI,MAAM;oBAAE,MAAM;gBAClB,IAAI,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;gBACnC,gBAAgB,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;gBAChD,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACxE,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;oBAAE,MAAM;gBAEzD,IAAI,IAAI,GAAG,UAAU,CAAC;gBACtB,MAAM,UAAU,GAAG,QAAQ,CAAC;oBAC1B,SAAS,EAAE,SAAsB;oBACjC,MAAM,EAAE,IAAI;oBACZ,WAAW,EAAE,KAAK;iBACnB,CAAC,CAAC;gBACH,gEAAgE;gBAChE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;oBACtB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;oBAClF,OAAO;gBACT,CAAC;gBACD,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM;gBACN,oBAAoB;YACtB,CAAC;YAED,8DAA8D;YAC9D;gBACE,MAAM;QACV,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,cAAc,GAAmB,EAAE,CAAC;IAC1C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACrB,MAAM,UAAU,GAAG,QAAQ,CAAC;YAC1B,SAAS,EAAE,SAAsB;YACjC,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,4BAA4B,QAAQ,2BAA2B,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QACD,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAAC,MAAyB,EAAE,QAAgB;IACzE,8EAA8E;IAC9E,IAAI,CAAC,MAAM,CAAC,mBAAmB;QAAE,OAAO,IAAI,CAAC;IAE7C,qEAAqE;IACrE,MAAM,kBAAkB,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAExD,KAAK,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACvF,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,kBAAkB,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACxD,OAAO,eAAe,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,GAAG,CAAC,MAAyB,EAAE,KAAa,EAAE,OAAY;IACxE,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/D,mEAAmE;IACnE,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;IACrG,MAAM,eAAe,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAE7D,MAAM,gBAAgB,GAAG,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACxD,MAAM,iBAAiB,GAAG,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE7D,IAAI,gBAAgB,GAAG,CAAC,IAAI,iBAAiB,GAAG,CAAC;QAAE,OAAO;IAC1D,IAAI,iBAAiB,GAAG,gBAAgB;QAAE,OAAO;IAEjD,uFAAuF;IACvF,IAAI,eAAe,KAAK,QAAQ;QAAE,OAAO;IAEzC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,CAAC,eAAsD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACpG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,eAAsD,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/index.cjs b/dist/index.cjs new file mode 100644 index 00000000..8a3366d9 --- /dev/null +++ b/dist/index.cjs @@ -0,0 +1,122283 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// dist/index.js +var index_exports = {}; +__export(index_exports, { + detectTests: () => detectTests, + schemas: () => schemas, + transformToSchemaKey: () => transformToSchemaKey, + validate: () => validate +}); +module.exports = __toCommonJS(index_exports); + +// dist/schemas/schemas.json +var schemas_default = { + checkLink_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + }, + click_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + config_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "config", + description: "Configuration options for Doc Detective operations.", + type: "object", + additionalProperties: false, + dynamicDefaults: { + configId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/config_v3.schema.json" + ] + }, + configId: { + description: "Identifier for the configuration.", + type: "string" + }, + configPath: { + description: "Path to the configuration file.", + type: "string", + readOnly: true + }, + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + default: ".", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + output: { + description: "Path of the directory in which to store the output of Doc Detective commands. If a file path is specified, Doc Detective attempts to honor the file name specified, but file path behavior is controlled by the configured reporters.", + type: "string", + default: "." + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specifications and source files.", + type: "boolean", + default: true + }, + relativePathBase: { + description: "Whether paths should be interpreted as relative to the current working directory (`cwd`) or to the file in which they're specified (`file`).", + type: "string", + enum: [ + "cwd", + "file" + ], + default: "file" + }, + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + }, + origin: { + description: "Default protocol and domain to use for relative URLs.", + type: "string" + }, + beforeAny: { + description: "Path(s) to test specifications to perform before those specified by `input`. Useful for setting up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + afterAll: { + description: "Path(s) to test specifications to perform after those specified by `input`. Useful for cleaning up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on defined markup.", + default: true + }, + allowUnsafeSteps: { + type: "boolean", + description: "Whether or not to run potentially unsafe steps, such as those that might modify files or system state." + }, + crawl: { + description: "If `true`, crawls sitemap.xml files specified by URL to find additional files to test.", + type: "boolean", + default: false + }, + processDitaMaps: { + description: "If `true`, processes DITA maps and includes generated files as inputs.", + type: "boolean", + default: true + }, + logLevel: { + description: "Amount of detail to output when performing an operation.", + type: "string", + enum: [ + "silent", + "error", + "warning", + "info", + "debug" + ], + default: "info" + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + fileTypes: { + description: "Configuration for file types and their markup detection.", + default: [ + "markdown", + "asciidoc", + "html", + "dita" + ], + anyOf: [ + { + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $comment: "Simple mode: Reference predefined templates by name.", + title: "File type (predefined)", + type: "string", + enum: [ + "markdown", + "asciidoc", + "html", + "dita" + ] + }, + { + $comment: "Custom mode: Extend predefined templates or write whole new ones.", + title: "File type (custom)", + type: "object", + anyOf: [ + { + required: [ + "extensions" + ] + }, + { + required: [ + "extends" + ] + } + ], + properties: { + name: { + description: "Name of the file type.", + type: "string" + }, + extends: { + description: "Base template to extend.", + type: "string", + enum: [ + "markdown", + "asciidoc", + "html" + ] + }, + extensions: { + description: "File extensions to use with type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + inlineStatements: { + description: "Statements to include tests and steps inside the content of the file, such as within Markdown.", + type: "object", + properties: { + testStart: { + description: "Regular expressions that indicate the start of a test. If capture groups are used, the first capture group is used for the statement. If no capture groups are used, the entire match is used for the statement.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + testEnd: { + description: "Regular expressions that indicate that the current test is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreStart: { + description: "Regular expressions that indicates that the following content should be ignored for testing purposes.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreEnd: { + description: "Regular expressions that indicate that the ignored section of content is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + step: { + description: "Regular expressions that indicate a step in a test.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + } + }, + title: "Inline statement definition" + }, + markup: { + description: "Markup definitions for the file type.", + type: "array", + minItems: 1, + items: { + type: "object", + properties: { + name: { + description: "Name of the markup definition", + type: "string" + }, + regex: { + description: "Regular expressions to match the markup type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + batchMatches: { + description: "If `true`, all matches are combined into a single string.", + type: "boolean", + default: false + }, + actions: { + description: "Actions to perform when the markup type is detected.", + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + ] + } + } + ] + } + }, + title: "Markup definition" + } + } + } + }, + { + title: "File type (executable)", + $comment: "Executable mode: Convert executable inputs directly into tests.", + type: "object", + required: [ + "extensions" + ], + properties: { + extensions: { + description: "File extensions to use with type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + runShell: { + description: "`runShell` step to perform for this file type. Use $1 as a placeholder for the file path.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + ] + } + } + } + ] + } + } + ] + }, + integrations: { + description: "Options for connecting to external services.", + type: "object", + additionalProperties: false, + properties: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + docDetectiveApi: { + type: "object", + description: "Configuration for Doc Detective Orchestration API integration.", + additionalProperties: false, + properties: { + apiKey: { + type: "string", + description: "API key for authenticating with the Doc Detective Orchestration API." + } + }, + title: "Doc Detective Orchestration API" + }, + heretto: { + type: "array", + description: "Configuration for Heretto CMS integrations. Each entry specifies a Heretto instance and a scenario to build and test.", + items: { + type: "object", + additionalProperties: false, + required: [ + "name", + "organizationId", + "username", + "apiToken" + ], + properties: { + name: { + type: "string", + description: "Unique identifier for this Heretto integration. Used in logs and results." + }, + organizationId: { + type: "string", + description: "The organization subdomain used to access Heretto CCMS (e.g., 'thunderbird' for thunderbird.heretto.com)." + }, + username: { + type: "string", + description: "Heretto CCMS username (email address) for API authentication." + }, + apiToken: { + format: "password", + minLength: 1, + type: "string", + description: "API token generated in Heretto CCMS for authentication. See https://help.heretto.com/en/heretto-ccms/api/ccms-api-authentication/basic-authentication#ariaid-title3" + }, + scenarioName: { + type: "string", + description: "Name of the scenario to build and test.", + default: "Doc Detective" + }, + outputPath: { + type: "string", + description: "Local path where Heretto content was downloaded. Set automatically during processing.", + readOnly: true + }, + fileMapping: { + type: "object", + description: "Mapping of local file paths to Heretto file metadata. Set automatically during content loading.", + readOnly: true, + additionalProperties: { + type: "object", + properties: { + fileId: { + type: "string", + description: "The UUID of the file in Heretto." + }, + filePath: { + type: "string", + description: "The path of the file in Heretto." + } + } + } + }, + uploadOnChange: { + type: "boolean", + description: "If `true`, uploads changed screenshots and other media files back to Heretto CMS after test execution.", + default: false + }, + resourceDependencies: { + type: "object", + description: "Mapping of Heretto file paths to their UUIDs and metadata. Set automatically during content loading by fetching ditamap resource dependencies.", + readOnly: true, + additionalProperties: { + type: "object", + properties: { + uuid: { + type: "string", + description: "The UUID of the file in Heretto." + }, + fullPath: { + type: "string", + description: "The full xmldb path of the file in Heretto." + }, + name: { + type: "string", + description: "The file name." + }, + parentFolderId: { + type: "string", + description: "The UUID of the parent folder in Heretto." + } + } + } + } + }, + title: "Heretto CMS integration" + }, + title: "Heretto CMS integrations" + } + }, + title: "Integrations options" + }, + telemetry: { + description: "Options around sending telemetry for Doc Detective usage.", + type: "object", + additionalProperties: false, + properties: { + send: { + description: "If `true`, sends Doc Detective telemetry.", + type: "boolean", + default: true + }, + userId: { + description: "Identifier for the organization, group, or individual running Doc Detective.", + type: "string" + } + }, + required: [ + "send" + ], + default: { + send: true + }, + title: "Telemetry options" + }, + concurrentRunners: { + type: [ + "integer", + "boolean" + ], + default: 1, + minimum: 1, + description: "Number of concurrent test runners. Set to true to use CPU core count (capped at 4).", + not: { + const: false + } + }, + environment: { + type: "object", + description: "Environment information for the system running Doc Detective.", + readOnly: true, + additionalProperties: false, + required: [ + "platform" + ], + properties: { + workingDirectory: { + description: "The current working directory of the process running Doc Detective.", + type: "string" + }, + platform: { + description: "The operating system type running Doc Detective.", + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + arch: { + description: "The processor architecture of the system running Doc Detective.", + type: "string", + enum: [ + "arm32", + "arm64", + "x32", + "x64" + ] + } + }, + title: "Environment details" + }, + debug: { + description: "Enable debugging mode. `true` allows pausing on breakpoints, waiting for user input before continuing. `stepThrough` pauses at every step, waiting for user input before continuing. `false` disables all debugging.", + anyOf: [ + { + type: "boolean" + }, + { + type: "string", + enum: [ + "stepThrough" + ] + } + ], + default: false + } + }, + components: { + schemas: { + environment: { + type: "object", + description: "Environment information for the system running Doc Detective.", + readOnly: true, + additionalProperties: false, + required: [ + "platform" + ], + properties: { + workingDirectory: { + description: "The current working directory of the process running Doc Detective.", + type: "string" + }, + platform: { + description: "The operating system type running Doc Detective.", + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + arch: { + description: "The processor architecture of the system running Doc Detective.", + type: "string", + enum: [ + "arm32", + "arm64", + "x32", + "x64" + ] + } + }, + title: "Environment details" + }, + markupDefinition: { + type: "object", + properties: { + name: { + description: "Name of the markup definition", + type: "string" + }, + regex: { + description: "Regular expressions to match the markup type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + batchMatches: { + description: "If `true`, all matches are combined into a single string.", + type: "boolean", + default: false + }, + actions: { + description: "Actions to perform when the markup type is detected.", + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + ] + } + } + ] + } + }, + title: "Markup definition" + }, + markupActionString: { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + inlineStatements: { + description: "Statements to include tests and steps inside the content of the file, such as within Markdown.", + type: "object", + properties: { + testStart: { + description: "Regular expressions that indicate the start of a test. If capture groups are used, the first capture group is used for the statement. If no capture groups are used, the entire match is used for the statement.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + testEnd: { + description: "Regular expressions that indicate that the current test is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreStart: { + description: "Regular expressions that indicates that the following content should be ignored for testing purposes.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreEnd: { + description: "Regular expressions that indicate that the ignored section of content is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + step: { + description: "Regular expressions that indicate a step in a test.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + } + }, + title: "Inline statement definition" + }, + stringOrArray: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + } + }, + examples: [ + {}, + { + input: ".", + output: ".", + recursive: true, + loadVariables: ".env", + fileTypes: [ + "markdown" + ] + }, + { + fileTypes: [ + { + extends: "markdown", + extensions: [ + "md", + "markdown", + "mdx" + ], + inlineStatements: { + testStart: "", + testEnd: "", + ignoreStart: "", + ignoreEnd: "", + step: "" + }, + markup: [ + { + name: "onscreenText", + regex: "\\*\\*.+?\\*\\*", + actions: "find" + } + ] + } + ] + }, + { + fileTypes: [ + { + name: "Jupyter Notebooks", + extensions: "ipynb", + runShell: { + command: "jupyter", + args: [ + "nbconvert", + "--to", + "script", + "--execute", + "$1", + "--stdout" + ] + } + }, + { + name: "JavaScript", + extensions: "js", + runShell: { + command: "node $1" + } + }, + { + name: "Python", + extensions: "py", + runShell: { + command: "python $1" + } + } + ] + }, + { + environment: { + platform: "windows", + arch: "x64" + } + }, + { + concurrentRunners: 1 + }, + { + concurrentRunners: true + }, + { + concurrentRunners: 4 + }, + { + debug: false + }, + { + debug: true + }, + { + debug: "stepThrough" + }, + { + integrations: { + docDetectiveApi: { + apiKey: "your-api-key-here" + } + } + }, + { + crawl: true + }, + { + processDitaMaps: true + }, + { + integrations: { + heretto: [ + { + name: "example", + organizationId: "your-organization-id", + username: "your-username", + apiToken: "your-api-token" + } + ] + } + } + ] + }, + context_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + }, + dragAndDrop_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + }, + find_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + }, + goTo_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + }, + loadCookie_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + }, + loadVariables_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + }, + httpRequest_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + }, + openApi_v3: { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + record_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + }, + resolvedTests_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "resolvedTests", + description: "A collection of resolved tests ready to be performed.", + type: "object", + dynamicDefaults: { + resolvedTestsId: "uuid" + }, + properties: { + resolvedTestsId: { + type: "string", + description: "Unique identifier for the resolved tests." + }, + config: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "config", + description: "Configuration options for Doc Detective operations.", + type: "object", + additionalProperties: false, + dynamicDefaults: { + configId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/config_v3.schema.json" + ] + }, + configId: { + description: "Identifier for the configuration.", + type: "string" + }, + configPath: { + description: "Path to the configuration file.", + type: "string", + readOnly: true + }, + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + default: ".", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + output: { + description: "Path of the directory in which to store the output of Doc Detective commands. If a file path is specified, Doc Detective attempts to honor the file name specified, but file path behavior is controlled by the configured reporters.", + type: "string", + default: "." + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specifications and source files.", + type: "boolean", + default: true + }, + relativePathBase: { + description: "Whether paths should be interpreted as relative to the current working directory (`cwd`) or to the file in which they're specified (`file`).", + type: "string", + enum: [ + "cwd", + "file" + ], + default: "file" + }, + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + }, + origin: { + description: "Default protocol and domain to use for relative URLs.", + type: "string" + }, + beforeAny: { + description: "Path(s) to test specifications to perform before those specified by `input`. Useful for setting up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + afterAll: { + description: "Path(s) to test specifications to perform after those specified by `input`. Useful for cleaning up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on defined markup.", + default: true + }, + allowUnsafeSteps: { + type: "boolean", + description: "Whether or not to run potentially unsafe steps, such as those that might modify files or system state." + }, + crawl: { + description: "If `true`, crawls sitemap.xml files specified by URL to find additional files to test.", + type: "boolean", + default: false + }, + processDitaMaps: { + description: "If `true`, processes DITA maps and includes generated files as inputs.", + type: "boolean", + default: true + }, + logLevel: { + description: "Amount of detail to output when performing an operation.", + type: "string", + enum: [ + "silent", + "error", + "warning", + "info", + "debug" + ], + default: "info" + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + fileTypes: { + description: "Configuration for file types and their markup detection.", + default: [ + "markdown", + "asciidoc", + "html", + "dita" + ], + anyOf: [ + { + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $comment: "Simple mode: Reference predefined templates by name.", + title: "File type (predefined)", + type: "string", + enum: [ + "markdown", + "asciidoc", + "html", + "dita" + ] + }, + { + $comment: "Custom mode: Extend predefined templates or write whole new ones.", + title: "File type (custom)", + type: "object", + anyOf: [ + { + required: [ + "extensions" + ] + }, + { + required: [ + "extends" + ] + } + ], + properties: { + name: { + description: "Name of the file type.", + type: "string" + }, + extends: { + description: "Base template to extend.", + type: "string", + enum: [ + "markdown", + "asciidoc", + "html" + ] + }, + extensions: { + description: "File extensions to use with type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + inlineStatements: { + description: "Statements to include tests and steps inside the content of the file, such as within Markdown.", + type: "object", + properties: { + testStart: { + description: "Regular expressions that indicate the start of a test. If capture groups are used, the first capture group is used for the statement. If no capture groups are used, the entire match is used for the statement.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + testEnd: { + description: "Regular expressions that indicate that the current test is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreStart: { + description: "Regular expressions that indicates that the following content should be ignored for testing purposes.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreEnd: { + description: "Regular expressions that indicate that the ignored section of content is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + step: { + description: "Regular expressions that indicate a step in a test.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + } + }, + title: "Inline statement definition" + }, + markup: { + description: "Markup definitions for the file type.", + type: "array", + minItems: 1, + items: { + type: "object", + properties: { + name: { + description: "Name of the markup definition", + type: "string" + }, + regex: { + description: "Regular expressions to match the markup type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + batchMatches: { + description: "If `true`, all matches are combined into a single string.", + type: "boolean", + default: false + }, + actions: { + description: "Actions to perform when the markup type is detected.", + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + ] + } + } + ] + } + }, + title: "Markup definition" + } + } + } + }, + { + title: "File type (executable)", + $comment: "Executable mode: Convert executable inputs directly into tests.", + type: "object", + required: [ + "extensions" + ], + properties: { + extensions: { + description: "File extensions to use with type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + runShell: { + description: "`runShell` step to perform for this file type. Use $1 as a placeholder for the file path.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + ] + } + } + } + ] + } + } + ] + }, + integrations: { + description: "Options for connecting to external services.", + type: "object", + additionalProperties: false, + properties: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + docDetectiveApi: { + type: "object", + description: "Configuration for Doc Detective Orchestration API integration.", + additionalProperties: false, + properties: { + apiKey: { + type: "string", + description: "API key for authenticating with the Doc Detective Orchestration API." + } + }, + title: "Doc Detective Orchestration API" + }, + heretto: { + type: "array", + description: "Configuration for Heretto CMS integrations. Each entry specifies a Heretto instance and a scenario to build and test.", + items: { + type: "object", + additionalProperties: false, + required: [ + "name", + "organizationId", + "username", + "apiToken" + ], + properties: { + name: { + type: "string", + description: "Unique identifier for this Heretto integration. Used in logs and results." + }, + organizationId: { + type: "string", + description: "The organization subdomain used to access Heretto CCMS (e.g., 'thunderbird' for thunderbird.heretto.com)." + }, + username: { + type: "string", + description: "Heretto CCMS username (email address) for API authentication." + }, + apiToken: { + format: "password", + minLength: 1, + type: "string", + description: "API token generated in Heretto CCMS for authentication. See https://help.heretto.com/en/heretto-ccms/api/ccms-api-authentication/basic-authentication#ariaid-title3" + }, + scenarioName: { + type: "string", + description: "Name of the scenario to build and test.", + default: "Doc Detective" + }, + outputPath: { + type: "string", + description: "Local path where Heretto content was downloaded. Set automatically during processing.", + readOnly: true + }, + fileMapping: { + type: "object", + description: "Mapping of local file paths to Heretto file metadata. Set automatically during content loading.", + readOnly: true, + additionalProperties: { + type: "object", + properties: { + fileId: { + type: "string", + description: "The UUID of the file in Heretto." + }, + filePath: { + type: "string", + description: "The path of the file in Heretto." + } + } + } + }, + uploadOnChange: { + type: "boolean", + description: "If `true`, uploads changed screenshots and other media files back to Heretto CMS after test execution.", + default: false + }, + resourceDependencies: { + type: "object", + description: "Mapping of Heretto file paths to their UUIDs and metadata. Set automatically during content loading by fetching ditamap resource dependencies.", + readOnly: true, + additionalProperties: { + type: "object", + properties: { + uuid: { + type: "string", + description: "The UUID of the file in Heretto." + }, + fullPath: { + type: "string", + description: "The full xmldb path of the file in Heretto." + }, + name: { + type: "string", + description: "The file name." + }, + parentFolderId: { + type: "string", + description: "The UUID of the parent folder in Heretto." + } + } + } + } + }, + title: "Heretto CMS integration" + }, + title: "Heretto CMS integrations" + } + }, + title: "Integrations options" + }, + telemetry: { + description: "Options around sending telemetry for Doc Detective usage.", + type: "object", + additionalProperties: false, + properties: { + send: { + description: "If `true`, sends Doc Detective telemetry.", + type: "boolean", + default: true + }, + userId: { + description: "Identifier for the organization, group, or individual running Doc Detective.", + type: "string" + } + }, + required: [ + "send" + ], + default: { + send: true + }, + title: "Telemetry options" + }, + concurrentRunners: { + type: [ + "integer", + "boolean" + ], + default: 1, + minimum: 1, + description: "Number of concurrent test runners. Set to true to use CPU core count (capped at 4).", + not: { + const: false + } + }, + environment: { + type: "object", + description: "Environment information for the system running Doc Detective.", + readOnly: true, + additionalProperties: false, + required: [ + "platform" + ], + properties: { + workingDirectory: { + description: "The current working directory of the process running Doc Detective.", + type: "string" + }, + platform: { + description: "The operating system type running Doc Detective.", + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + arch: { + description: "The processor architecture of the system running Doc Detective.", + type: "string", + enum: [ + "arm32", + "arm64", + "x32", + "x64" + ] + } + }, + title: "Environment details" + }, + debug: { + description: "Enable debugging mode. `true` allows pausing on breakpoints, waiting for user input before continuing. `stepThrough` pauses at every step, waiting for user input before continuing. `false` disables all debugging.", + anyOf: [ + { + type: "boolean" + }, + { + type: "string", + enum: [ + "stepThrough" + ] + } + ], + default: false + } + }, + components: { + schemas: { + environment: { + type: "object", + description: "Environment information for the system running Doc Detective.", + readOnly: true, + additionalProperties: false, + required: [ + "platform" + ], + properties: { + workingDirectory: { + description: "The current working directory of the process running Doc Detective.", + type: "string" + }, + platform: { + description: "The operating system type running Doc Detective.", + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + arch: { + description: "The processor architecture of the system running Doc Detective.", + type: "string", + enum: [ + "arm32", + "arm64", + "x32", + "x64" + ] + } + }, + title: "Environment details" + }, + markupDefinition: { + type: "object", + properties: { + name: { + description: "Name of the markup definition", + type: "string" + }, + regex: { + description: "Regular expressions to match the markup type.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + batchMatches: { + description: "If `true`, all matches are combined into a single string.", + type: "boolean", + default: false + }, + actions: { + description: "Actions to perform when the markup type is detected.", + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + ] + } + } + ] + } + }, + title: "Markup definition" + }, + markupActionString: { + type: "string", + enum: [ + "checkLink", + "click", + "find", + "goTo", + "httpRequest", + "loadCookie", + "loadVariables", + "record", + "runCode", + "runShell", + "saveCookie", + "screenshot", + "stopRecord", + "type", + "wait" + ] + }, + inlineStatements: { + description: "Statements to include tests and steps inside the content of the file, such as within Markdown.", + type: "object", + properties: { + testStart: { + description: "Regular expressions that indicate the start of a test. If capture groups are used, the first capture group is used for the statement. If no capture groups are used, the entire match is used for the statement.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + testEnd: { + description: "Regular expressions that indicate that the current test is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreStart: { + description: "Regular expressions that indicates that the following content should be ignored for testing purposes.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + ignoreEnd: { + description: "Regular expressions that indicate that the ignored section of content is complete.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + }, + step: { + description: "Regular expressions that indicate a step in a test.", + anyOf: [ + { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + ] + } + }, + title: "Inline statement definition" + }, + stringOrArray: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + minItems: 1, + items: { + type: "string" + } + } + ] + } + } + }, + examples: [ + {}, + { + input: ".", + output: ".", + recursive: true, + loadVariables: ".env", + fileTypes: [ + "markdown" + ] + }, + { + fileTypes: [ + { + extends: "markdown", + extensions: [ + "md", + "markdown", + "mdx" + ], + inlineStatements: { + testStart: "", + testEnd: "", + ignoreStart: "", + ignoreEnd: "", + step: "" + }, + markup: [ + { + name: "onscreenText", + regex: "\\*\\*.+?\\*\\*", + actions: "find" + } + ] + } + ] + }, + { + fileTypes: [ + { + name: "Jupyter Notebooks", + extensions: "ipynb", + runShell: { + command: "jupyter", + args: [ + "nbconvert", + "--to", + "script", + "--execute", + "$1", + "--stdout" + ] + } + }, + { + name: "JavaScript", + extensions: "js", + runShell: { + command: "node $1" + } + }, + { + name: "Python", + extensions: "py", + runShell: { + command: "python $1" + } + } + ] + }, + { + environment: { + platform: "windows", + arch: "x64" + } + }, + { + concurrentRunners: 1 + }, + { + concurrentRunners: true + }, + { + concurrentRunners: 4 + }, + { + debug: false + }, + { + debug: true + }, + { + debug: "stepThrough" + }, + { + integrations: { + docDetectiveApi: { + apiKey: "your-api-key-here" + } + } + }, + { + crawl: true + }, + { + processDitaMaps: true + }, + { + integrations: { + heretto: [ + { + name: "example", + organizationId: "your-organization-id", + username: "your-username", + apiToken: "your-api-token" + } + ] + } + } + ] + }, + specs: { + description: "Test specifications that were performed.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "specification", + type: "object", + dynamicDefaults: { + specId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/spec_v3.schema.json" + ] + }, + specId: { + type: "string", + description: "Unique identifier for the test specification." + }, + description: { + type: "string", + description: "Description of the test specification." + }, + specPath: { + type: "string", + description: "Path to the test specification." + }, + contentPath: { + type: "string", + description: "Path to the content that the specification is associated with." + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + tests: { + description: "[Tests](test) to perform.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + testId: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + contentPath: { + type: "string", + description: "Path to the content that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex.", + default: true + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + before: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + after: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + }, + contexts: { + title: "Resolved contexts", + type: "array", + readOnly: true, + description: "Resolved contexts to run the test in. This is a resolved version of the `runOn` property. It is not user-defined and should not be used in test specifications.", + items: { + type: "object", + properties: { + platform: { + type: "string", + description: "Platform to run the test on. This is a resolved version of the `platforms` property." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + } + }, + title: "Resolved context" + } + } + }, + dynamicDefaults: { + testId: "uuid" + }, + anyOf: [ + { + required: [ + "steps" + ] + }, + { + required: [ + "contexts" + ] + } + ], + additionalProperties: false, + components: { + schemas: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + } + } + }, + examples: [ + { + steps: [ + { + checkLink: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + click: true, + type: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + } + ] + }, + { + testId: "Do all the things! - Test", + description: "This test includes every property across all actions.", + before: "setup.json", + after: "cleanup.json", + runOn: [ + { + platforms: [ + "linux" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + }, + { + testId: "c61b02e8-7485-44d3-8065-f873673379c6", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "", + name: "Acme" + } + ], + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ], + detectSteps: true + } + ] + } + ] + } + } + }, + required: [ + "tests" + ], + examples: [ + { + tests: [ + { + steps: [ + { + checkLink: { + url: "https://www.duckduckgo.com" + } + } + ] + } + ] + }, + { + specId: "Do all the things! - Spec", + runOn: [ + { + platforms: [ + "windows", + "mac" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + tests: [ + { + testId: "Do all the things! - Test", + description: "This test includes nearly every property across all actions.", + runOn: [ + { + platforms: "linux", + browsers: "firefox" + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + } + ] + }, + { + specId: "Make a request from an OpenAPI definition", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + name: "Acme" + } + ], + tests: [ + { + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ] + } + ] + } + ] + } + ] + } + } + }, + required: [ + "specs" + ], + examples: [ + { + config: { + input: [ + "/home/hawkeyexl/Workspaces/resolver/dev/doc-content-yaml.md" + ], + logLevel: "debug", + output: ".", + recursive: true, + relativePathBase: "file", + detectSteps: true, + fileTypes: [ + { + name: "markdown", + extensions: [ + "md", + "markdown", + "mdx" + ], + inlineStatements: { + testStart: [ + "{\\/\\*\\s*test\\s+?([\\s\\S]*?)\\s*\\*\\/}", + "", + "\\[comment\\]:\\s+#\\s+\\(test\\s*(.*?)\\s*\\)", + "\\[comment\\]:\\s+#\\s+\\(test start\\s*(.*?)\\s*\\)" + ], + testEnd: [ + "{\\/\\*\\s*test end\\s*\\*\\/}", + "", + "\\[comment\\]:\\s+#\\s+\\(test end\\)" + ], + ignoreStart: [ + "{\\/\\*\\s*test ignore start\\s*\\*\\/}", + "" + ], + ignoreEnd: [ + "{\\/\\*\\s*test ignore end\\s*\\*\\/}", + "" + ], + step: [ + "{\\/\\*\\s*step\\s+?([\\s\\S]*?)\\s*\\*\\/}", + "", + "\\[comment\\]:\\s+#\\s+\\(step\\s*(.*?)\\s*\\)" + ] + }, + markup: [ + { + name: "checkHyperlink", + regex: [ + '(?" + ], + testEnd: [ + "" + ], + ignoreStart: [ + "" + ], + ignoreEnd: [ + "" + ], + step: [ + "" + ] + }, + markup: [] + } + ], + telemetry: { + send: true + }, + configId: "3e467e5d-27cb-41f3-800f-aeb3c20dcb4c", + environment: { + arch: "x64", + platform: "linux", + workingDirectory: "/home/hawkeyexl/Workspaces/resolver" + } + }, + specs: [ + { + specId: "cc656bba-132f-4f0f-b093-2cfbdd784f69", + contentPath: "/home/hawkeyexl/Workspaces/resolver/dev/doc-content-yaml.md", + tests: [ + { + testId: "doc-detective-docs", + detectSteps: false, + runOn: [], + openApi: [], + contexts: [ + { + steps: [ + { + checkLink: "https://doc-detective.com" + }, + { + checkLink: "https://doc-detective.com/docs/get-started/intro" + }, + { + goTo: "https://doc-detective.com/docs/get-started/actions/type" + }, + { + find: "Special keys" + }, + { + screenshot: "reference.png" + } + ], + contextId: "eec1d123-7dfd-4362-b41a-942f36e0da5a" + } + ] + } + ], + runOn: [], + openApi: [] + } + ] + } + ] + }, + report_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "report", + type: "object", + dynamicDefaults: { + reportId: "uuid" + }, + properties: { + reportId: { + type: "string", + description: "Unique identifier for the test specification." + }, + specs: { + description: "Test specifications that were performed.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "specification", + type: "object", + dynamicDefaults: { + specId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/spec_v3.schema.json" + ] + }, + specId: { + type: "string", + description: "Unique identifier for the test specification." + }, + description: { + type: "string", + description: "Description of the test specification." + }, + specPath: { + type: "string", + description: "Path to the test specification." + }, + contentPath: { + type: "string", + description: "Path to the content that the specification is associated with." + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + tests: { + description: "[Tests](test) to perform.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + testId: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + contentPath: { + type: "string", + description: "Path to the content that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex.", + default: true + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + before: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + after: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + }, + contexts: { + title: "Resolved contexts", + type: "array", + readOnly: true, + description: "Resolved contexts to run the test in. This is a resolved version of the `runOn` property. It is not user-defined and should not be used in test specifications.", + items: { + type: "object", + properties: { + platform: { + type: "string", + description: "Platform to run the test on. This is a resolved version of the `platforms` property." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + } + }, + title: "Resolved context" + } + } + }, + dynamicDefaults: { + testId: "uuid" + }, + anyOf: [ + { + required: [ + "steps" + ] + }, + { + required: [ + "contexts" + ] + } + ], + additionalProperties: false, + components: { + schemas: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + } + } + }, + examples: [ + { + steps: [ + { + checkLink: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + click: true, + type: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + } + ] + }, + { + testId: "Do all the things! - Test", + description: "This test includes every property across all actions.", + before: "setup.json", + after: "cleanup.json", + runOn: [ + { + platforms: [ + "linux" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + }, + { + testId: "c61b02e8-7485-44d3-8065-f873673379c6", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "", + name: "Acme" + } + ], + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ], + detectSteps: true + } + ] + } + ] + } + } + }, + required: [ + "tests" + ], + examples: [ + { + tests: [ + { + steps: [ + { + checkLink: { + url: "https://www.duckduckgo.com" + } + } + ] + } + ] + }, + { + specId: "Do all the things! - Spec", + runOn: [ + { + platforms: [ + "windows", + "mac" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + tests: [ + { + testId: "Do all the things! - Test", + description: "This test includes nearly every property across all actions.", + runOn: [ + { + platforms: "linux", + browsers: "firefox" + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + } + ] + }, + { + specId: "Make a request from an OpenAPI definition", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + name: "Acme" + } + ], + tests: [ + { + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ] + } + ] + } + ] + } + ] + } + } + }, + required: [ + "specs" + ], + examples: [ + { + specs: [ + { + tests: [ + { + steps: [ + { + checkLink: { + url: "https://www.duckduckgo.com" + } + } + ] + } + ] + } + ] + }, + { + specs: [ + { + specId: "Do all the things! - Spec", + runOn: [ + { + platforms: [ + "windows", + "mac" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + tests: [ + { + testId: "Do all the things! - Test", + description: "This test includes nearly every property across all actions.", + runOn: [ + { + platforms: "linux", + browsers: "firefox" + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + } + ] + } + ] + }, + { + specs: [ + { + specId: "Make a request from an OpenAPI definition", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + name: "Acme" + } + ], + tests: [ + { + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ] + } + ] + } + ] + } + ] + }, + runCode_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + }, + runShell_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + }, + saveCookie_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + }, + screenshot_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + }, + sourceIntegration_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + description: "Information about the source integration for a file, enabling upload of changed files back to the source CMS.", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + }, + spec_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "specification", + type: "object", + dynamicDefaults: { + specId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/spec_v3.schema.json" + ] + }, + specId: { + type: "string", + description: "Unique identifier for the test specification." + }, + description: { + type: "string", + description: "Description of the test specification." + }, + specPath: { + type: "string", + description: "Path to the test specification." + }, + contentPath: { + type: "string", + description: "Path to the content that the specification is associated with." + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + tests: { + description: "[Tests](test) to perform.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + testId: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + contentPath: { + type: "string", + description: "Path to the content that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex.", + default: true + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + before: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + after: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + }, + contexts: { + title: "Resolved contexts", + type: "array", + readOnly: true, + description: "Resolved contexts to run the test in. This is a resolved version of the `runOn` property. It is not user-defined and should not be used in test specifications.", + items: { + type: "object", + properties: { + platform: { + type: "string", + description: "Platform to run the test on. This is a resolved version of the `platforms` property." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + } + }, + title: "Resolved context" + } + } + }, + dynamicDefaults: { + testId: "uuid" + }, + anyOf: [ + { + required: [ + "steps" + ] + }, + { + required: [ + "contexts" + ] + } + ], + additionalProperties: false, + components: { + schemas: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + } + } + }, + examples: [ + { + steps: [ + { + checkLink: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + click: true, + type: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + } + ] + }, + { + testId: "Do all the things! - Test", + description: "This test includes every property across all actions.", + before: "setup.json", + after: "cleanup.json", + runOn: [ + { + platforms: [ + "linux" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + }, + { + testId: "c61b02e8-7485-44d3-8065-f873673379c6", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "", + name: "Acme" + } + ], + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ], + detectSteps: true + } + ] + } + ] + } + } + }, + required: [ + "tests" + ], + examples: [ + { + tests: [ + { + steps: [ + { + checkLink: { + url: "https://www.duckduckgo.com" + } + } + ] + } + ] + }, + { + specId: "Do all the things! - Spec", + runOn: [ + { + platforms: [ + "windows", + "mac" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + tests: [ + { + testId: "Do all the things! - Test", + description: "This test includes nearly every property across all actions.", + runOn: [ + { + platforms: "linux", + browsers: "firefox" + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + } + ] + }, + { + specId: "Make a request from an OpenAPI definition", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + name: "Acme" + } + ], + tests: [ + { + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ] + } + ] + } + ] + }, + step_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + }, + stopRecord_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + }, + test_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + testId: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + contentPath: { + type: "string", + description: "Path to the content that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex.", + default: true + }, + runOn: { + type: "array", + description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.", + items: { + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "context", + type: "object", + description: "A context in which to perform tests. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For example, if a browser isn't specified but is required by steps in the test, Doc Detective will search for and use a supported browser available in the current environment.", + additionalProperties: false, + dynamicDefaults: { + contextId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/context_v3.schema.json" + ] + }, + contextId: { + type: "string", + description: "Unique identifier for the context." + }, + platforms: { + description: "Platforms to run tests on.", + anyOf: [ + { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + { + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + ] + }, + browsers: { + description: "Browsers to run tests on.", + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + ] + } + } + ] + } + }, + components: { + schemas: { + platform: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + }, + browserName: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + } + } + }, + examples: [ + { + platforms: "linux", + browsers: "chrome" + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + "chrome", + "firefox", + "webkit" + ] + }, + { + browsers: { + name: "chrome", + headless: true + } + }, + { + browsers: [ + { + name: "chrome", + headless: true + }, + { + name: "firefox" + } + ] + }, + { + platforms: [ + "mac", + "linux" + ], + browsers: { + name: "chrome", + headless: true + } + }, + { + platforms: [ + "windows", + "mac", + "linux" + ], + browsers: [ + { + name: "chrome", + headless: true, + window: { + width: 1920, + height: 1080 + }, + viewport: { + width: 1600, + height: 900 + } + }, + { + name: "firefox", + window: { + width: 1366, + height: 768 + } + }, + { + name: "webkit", + headless: false, + viewport: { + width: 1440, + height: 900 + } + } + ] + }, + { + platforms: "mac", + browsers: [ + { + name: "safari", + window: { + width: 1280, + height: 800 + } + } + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + before: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + after: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + }, + contexts: { + title: "Resolved contexts", + type: "array", + readOnly: true, + description: "Resolved contexts to run the test in. This is a resolved version of the `runOn` property. It is not user-defined and should not be used in test specifications.", + items: { + type: "object", + properties: { + platform: { + type: "string", + description: "Platform to run the test on. This is a resolved version of the `platforms` property." + }, + browser: { + type: "object", + description: "Browser configuration.", + required: [ + "name" + ], + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the browser.", + enum: [ + "chrome", + "firefox", + "safari", + "webkit" + ], + $comment: "`safari` is just a shortcut for `webkit`. Included for visibility and to reduce questions." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode.", + default: true + }, + window: { + type: "object", + description: "Browser dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the browser window in pixels." + }, + height: { + type: "integer", + description: "Height of the browser window in pixels." + } + }, + title: "Browser Window" + }, + viewport: { + type: "object", + description: "Viewport dimensions.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the viewport in pixels." + }, + height: { + type: "integer", + description: "Height of the viewport in pixels." + } + }, + title: "Browser Viewport" + } + }, + title: "Browser" + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + }, + steps: { + description: "Steps to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails. By default, if a step fails, the test stops and the remaining steps are not executed.", + type: "array", + minItems: 1, + items: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "step", + description: "A step in a test.", + type: "object", + components: { + schemas: { + common: { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + } + }, + anyOf: [ + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "checkLink" + ], + properties: { + checkLink: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "checkLink", + anyOf: [ + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "string", + pattern: "(^(http://|https://|\\/).*|\\$[A-Za-z0-9_]+$)", + transform: [ + "trim" + ] + }, + object: { + title: "Check link (detailed)", + description: "Check if an HTTP or HTTPS URL returns an acceptable status code from a GET request.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to check. Can be a full URL or a path. If a path is provided, `origin` must be specified.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + anyOf: [ + { + type: "integer" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + } + } + ], + default: [ + 200, + 301, + 302, + 307, + 308 + ] + } + } + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com", + statusCodes: 200 + }, + { + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + } + }, + title: "checkLink" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "click" + ], + properties: { + click: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + } + }, + title: "click" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "find" + ], + properties: { + find: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "find", + description: "Find an element based on display text or a selector, then optionally interact with it.", + anyOf: [ + { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + ], + components: { + schemas: { + string: { + title: "Find element (simple)", + type: "string", + description: "Identifier for the element to find. Can be a selector, element text, ARIA name, ID, or test ID." + }, + object: { + title: "Find element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + additionalProperties: false, + properties: { + elementText: { + type: "string", + description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to find. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view.", + type: "boolean", + default: true + }, + click: { + description: "Click the element.", + anyOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "click", + description: "Click or tap an element.", + anyOf: [ + { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + }, + { + type: "boolean" + } + ], + components: { + schemas: { + string: { + title: "Click element (simple)", + type: "string", + description: "Identifier for the element to click. Can be a selector, element text, ARIA name, ID, or test ID." + }, + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + object: { + title: "Click element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + }, + elementText: { + type: "string", + description: "Display text of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + selector: { + type: "string", + description: "Selector of the element to click. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to click. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + }, + examples: [ + true, + "right", + { + button: "left", + elementText: "Element text" + }, + { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + ] + }, + { + type: "object", + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + }, + title: "Find element and click" + } + ] + }, + type: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`type`](type). To type in the element, make the element active with the `click` parameter.", + allOf: [ + { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + { + not: { + type: "object", + required: [ + "selector", + "elementText", + "elementId", + "elementTestId", + "elementClass", + "elementAttribute", + "elementAria" + ], + title: "Find element and type" + } + } + ] + } + } + } + } + }, + examples: [ + "Find me!", + { + selector: "[title=Search]" + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + }, + { + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + }, + { + elementId: "/^user-[0-9]+$/", + elementClass: [ + "admin", + "/^level-[1-5]$/" + ], + elementAttribute: { + "data-active": true, + "data-score": "/^[0-9]+$/" + }, + timeout: 8e3, + moveTo: false + } + ] + } + }, + title: "find" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "goTo" + ], + properties: { + goTo: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "goTo", + anyOf: [ + { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Go to URL (simple)", + description: "Navigate to an HTTP or HTTPS URL. Can be a full URL or a path. If a path is provided, navigates relative to the current URL, if any.", + type: "string", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + description: "Navigate to an HTTP or HTTPS URL.", + type: "object", + additionalProperties: false, + required: [ + "url" + ], + properties: { + url: { + type: "string", + description: "URL to navigate to. Can be a full URL or a path. If a path is provided and `origin` is specified, prepends `origin` to `url`. If a path is provided but `origin` isn't specified, attempts to navigate relative to the current URL, if any.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + timeout: { + type: "integer", + description: "Maximum time in milliseconds to wait for the page to be ready. If exceeded, the goTo action fails.", + default: 3e4, + minimum: 0 + }, + waitUntil: { + type: "object", + description: "Configuration for waiting conditions after navigation.", + additionalProperties: false, + properties: { + networkIdleTime: { + description: "Wait for network activity to be idle (no new requests) for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 500 + }, + domIdleTime: { + description: "Wait for DOM mutations to stop for this duration in milliseconds. Set to `null` to skip this check.", + anyOf: [ + { + type: "integer", + minimum: 0 + }, + { + type: "null" + } + ], + default: 1e3 + }, + find: { + type: "object", + description: "Wait for a specific element to be present in the DOM. At least one of selector or elementText must be specified.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + selector: { + type: "string", + description: "CSS selector for the element to wait for." + }, + elementText: { + type: "string", + description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + } + } + } + } + }, + title: "Go to URL (detailed)" + } + } + }, + examples: [ + "https://www.google.com", + "/search", + { + url: "https://www.google.com" + }, + { + url: "/search", + origin: "https://www.google.com" + }, + { + url: "https://www.example.com", + waitUntil: { + networkIdleTime: 500 + } + }, + { + url: "https://www.example.com/dashboard", + waitUntil: { + find: { + selector: "[data-testid='dashboard-loaded']" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: 500, + domIdleTime: 1e3, + find: { + selector: ".main-content", + elementText: "Dashboard" + } + } + }, + { + url: "https://www.example.com/app", + timeout: 6e4, + waitUntil: { + networkIdleTime: null + } + }, + { + url: "https://www.example.com/status", + waitUntil: { + find: { + elementText: "System operational" + } + } + }, + { + url: "http://localhost:8092", + waitUntil: { + find: { + selector: "button", + elementText: "Standard Button", + elementTestId: "standard-btn", + elementAria: "Sample Standard Button", + elementId: "standard-btn", + elementClass: [ + "btn" + ], + elementAttribute: { + type: "button", + value: "Standard Button" + } + } + } + } + ] + } + }, + title: "goTo" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "httpRequest" + ], + properties: { + httpRequest: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "httpRequest", + description: "Perform a generic HTTP request, for example to an API.", + anyOf: [ + { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + ], + components: { + schemas: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + title: "HTTP request (detailed)", + type: "object", + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + additionalProperties: false, + properties: { + url: { + title: "HTTP request (simple)", + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + anyOf: [ + { + allOf: [ + { + type: "string", + description: "ID of the operation to use for the request." + }, + { + title: "Operation ID", + description: "Operation ID from the OpenAPI schema. Only valid if the OpenAPI description path is specified elsewhere and the operation ID is unique among all specified OpenAPI descriptions." + } + ] + }, + { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + required: [ + "operationId" + ], + title: "OpenAPI definition (httpRequest)" + } + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + request: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers to include in the HTTP request.", + default: {}, + anyOf: [ + { + title: "Request headers (object)", + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request headers (string)", + description: "Headers to include in the HTTP request, as return-separated values. For example, `Content-Type: application/json\nAuthorization: Bearer token`.", + type: "string" + } + ] + }, + parameters: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {}, + title: "Request parameters" + }, + body: { + description: "The body of the HTTP request.", + anyOf: [ + { + title: "Request body (object)", + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + properties: {} + }, + { + title: "Request body (array)", + description: "JSON array to include as the body of the HTTP request.", + type: "array", + items: {} + }, + { + title: "Request body (string)", + description: "String to include as the body of the HTTP request.", + type: "string" + } + ], + default: {} + } + }, + title: "Request" + }, + response: { + type: "object", + additionalProperties: false, + properties: { + headers: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {}, + title: "Response headers" + }, + body: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + anyOf: [ + { + type: "object", + additionalProperties: true, + properties: {}, + title: "Response body object", + description: "JSON key/value pairs expected in the response." + }, + { + title: "Response body array", + description: "JSON array expected in the response.", + type: "array", + items: {} + }, + { + title: "Response body string", + description: "String expected in the response.", + type: "string" + } + ], + default: {} + }, + required: { + type: "array", + description: "Array of field paths that must exist in the response body. Uses dot notation for nested fields (e.g., 'user.name') and bracket notation for array indices (e.g., 'items[0].id'). Fields must be present but may have any value including null.", + items: { + type: "string" + }, + default: [] + } + }, + title: "Response" + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in the response body.", + default: true + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + } + } + } + } + }, + examples: [ + "https://reqres.in/api/users", + { + url: "https://reqres.in/api/users" + }, + { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + }, + { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + }, + { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + }, + { + openApi: "getUserById" + }, + { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + }, + { + url: "https://www.api-server.com", + method: "post", + request: { + headers: "Content-Type: application/json\\nAuthorization: Bearer token" + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "id", + "email", + "createdAt" + ] + } + }, + { + url: "https://api.example.com/users/123", + response: { + required: [ + "user.profile.name", + "user.profile.avatar", + "user.settings.notifications" + ] + } + }, + { + url: "https://api.example.com/orders", + response: { + required: [ + "orders[0].id", + "orders[0].total", + "orders[0].items[0].productId" + ] + } + }, + { + url: "https://api.example.com/users", + response: { + required: [ + "sessionToken", + "expiresAt", + "user.id" + ], + body: { + status: "success", + user: { + role: "admin" + } + } + } + } + ] + } + }, + title: "httpRequest" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runShell" + ], + properties: { + runShell: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runShell", + description: "Perform a native shell command.", + anyOf: [ + { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + ], + components: { + schemas: { + string: { + title: "Run shell command (simple)", + description: "Command to perform in the machine's default shell.", + type: "string", + transform: [ + "trim" + ] + }, + object: { + type: "object", + required: [ + "command" + ], + additionalProperties: false, + properties: { + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's stdout or stderr. If the expected content can't be found in the command's stdout or stderr, the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run shell command (detailed)" + } + } + }, + examples: [ + "docker run hello-world", + { + command: "echo", + args: [ + "$USER" + ] + }, + { + command: "echo", + args: [ + "hello-world" + ] + }, + { + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + command: "false", + exitCodes: [ + 1 + ] + }, + { + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + stdio: "/.*?/" + }, + { + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!", + path: "docker-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runShell" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "runCode" + ], + properties: { + runCode: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "runCode", + description: "Assemble and run code.", + anyOf: [ + { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + ], + components: { + schemas: { + object: { + type: "object", + required: [ + "code", + "language" + ], + properties: { + language: { + type: "string", + description: "Language of the code to run.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + description: "Code to run.", + type: "string" + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + anyOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + anyOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + stdio: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + path: { + type: "string", + description: "File path to save the command's output, relative to `directory`." + }, + directory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `path`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `path` if it exists.\nIf `aboveVariation`, overwrites the existing output at `path` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + } + }, + title: "Run code (detailed)" + } + } + }, + examples: [ + { + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + stdio: "Hello from Docker!" + }, + { + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + ] + } + }, + title: "runCode" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "type" + ], + properties: { + type: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + } + }, + title: "type" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "screenshot" + ], + properties: { + screenshot: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "screenshot", + description: "Takes a screenshot in PNG format.", + anyOf: [ + { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + { + type: "boolean", + title: "Capture screenshot", + description: "If `true`, captures a screenshot. If `false`, doesn't capture a screenshot." + } + ], + components: { + schemas: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + path: { + title: "Screenshot (simple)", + description: "File path of the PNG file. Accepts absolute paths. If not specified, the file name is the ID of the step.", + type: "string", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the existing screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 0.05, + minimum: 0, + maximum: 1 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `aboveVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "aboveVariation" + ], + default: "aboveVariation" + }, + crop: { + anyOf: [ + { + title: "Crop by element (simple)", + type: "string", + description: "Display text or selector of the element to screenshot." + }, + { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + } + ] + }, + sourceIntegration: { + description: "Information about the source integration for this screenshot, enabling upload of changed files back to the source CMS. Set automatically during test resolution for files from integrations.", + $schema: "http://json-schema.org/draft-07/schema#", + title: "sourceIntegration", + type: "object", + additionalProperties: false, + required: [ + "type", + "integrationName" + ], + properties: { + type: { + type: "string", + description: "The type of integration. Currently supported: 'heretto'. Additional types may be added in the future.", + enum: [ + "heretto" + ] + }, + integrationName: { + type: "string", + description: "The name of the integration configuration in the config file. Used to look up authentication credentials." + }, + fileId: { + type: "string", + description: "The unique identifier (UUID) of the file in the source CMS. If not provided, the file will be looked up by path." + }, + filePath: { + type: "string", + description: "The path of the file in the source CMS. Used for lookup if fileId is not available." + }, + contentPath: { + type: "string", + description: "The local path to the file that references this source. Used for resolving relative paths." + } + }, + examples: [ + { + type: "heretto", + integrationName: "my-heretto", + fileId: "8f3ed200-cbba-11e1-ac51-c82a1446d15c", + filePath: "/db/organizations/example/repositories/docs/images/screenshot.png" + }, + { + type: "heretto", + integrationName: "my-heretto", + filePath: "images/screenshot.png", + contentPath: "/tmp/doc-detective/heretto_abc123/topic.dita" + } + ] + } + }, + title: "Capture screenshot (detailed)" + }, + crop_element: { + title: "Crop by element (detailed)", + type: "object", + description: "Crop the screenshot to a specific element.", + additionalProperties: false, + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text of the element to screenshot." + }, + selector: { + type: "string", + description: "Selector of the element to screenshot." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + padding: { + anyOf: [ + { + title: "Padding (simple)", + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + ] + } + } + }, + padding: { + type: "object", + additionalProperties: false, + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + }, + title: "Padding (detailed)", + description: "Padding in pixels to add to the bounds of the element." + } + } + }, + examples: [ + true, + "image.png", + "static/images/image.png", + "/User/manny/projects/doc-detective/static/images/image.png", + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + }, + { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + ] + } + }, + title: "screenshot" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "saveCookie" + ], + properties: { + saveCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "saveCookie", + description: "Save a specific browser cookie to a file or environment variable for later reuse.", + anyOf: [ + { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save. Will be saved to a default file path or environment variable.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to save.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name to store the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to save the cookie, relative to directory. Uses Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory to save the cookie file. If not specified, uses output directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "boolean", + title: "Overwrite existing file", + description: "Whether to overwrite existing cookie file.", + default: false + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by (optional).", + transform: [ + "trim" + ] + } + }, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + title: "Save cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "test_env_cookie", + { + name: "auth_cookie", + path: "auth-cookie.txt" + }, + { + name: "session_token", + variable: "SESSION_TOKEN" + }, + { + name: "test_cookie", + path: "test-cookie.txt", + overwrite: true + }, + { + name: "user_session", + path: "user-session.txt", + directory: "./test-data", + overwrite: true + }, + { + name: "login_token", + path: "login-token.txt", + domain: "app.example.com" + } + ] + } + }, + title: "saveCookie" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "record" + ], + properties: { + record: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "record", + description: "Start recording the current browser viewport. Must be followed by a `stopRecord` step. Only runs in Chrome browsers when they are visible. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + anyOf: [ + { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + }, + { + type: "boolean", + title: "Record (boolean)", + description: "If `true`, records the current browser viewport. If `false`, doesn't record the current browser viewport." + } + ], + components: { + schemas: { + string: { + title: "Record (simple)", + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + object: { + type: "object", + properties: { + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + description: "Directory of the file. If the directory doesn't exist, creates the directory.", + transform: [ + "trim" + ] + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing recording at `path` if it exists.", + enum: [ + "true", + "false" + ] + } + }, + title: "Record (detailed)" + } + } + }, + examples: [ + true, + "results.mp4", + { + path: "results.mp4", + directory: "static/media", + overwrite: "true" + } + ] + } + }, + title: "record" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + type: "object", + required: [ + "stopRecord" + ], + properties: { + stopRecord: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "stopRecord", + description: "Stop the current recording.", + anyOf: [ + { + type: "boolean", + nullable: true + } + ], + examples: [ + true + ] + } + }, + title: "stopRecord" + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadVariables", + type: "object", + required: [ + "loadVariables" + ], + properties: { + loadVariables: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadVariables", + type: "string", + description: "Load environment variables from the specified `.env` file.", + examples: [ + ".env" + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "dragAndDrop", + type: "object", + required: [ + "dragAndDrop" + ], + properties: { + dragAndDrop: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "dragAndDrop", + description: "Drag and drop an element from source to target.", + type: "object", + required: [ + "source", + "target" + ], + properties: { + source: { + description: "The element to drag.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + target: { + description: "The target location to drop the element.", + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + duration: { + type: "integer", + description: "Duration of the drag operation in milliseconds.", + default: 1e3, + minimum: 0 + } + }, + components: { + schemas: { + elementSpecification: { + anyOf: [ + { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + ] + }, + string: { + title: "Element (simple)", + type: "string", + description: "Display text, selector, or regex pattern (enclosed in forward slashes) of the element." + }, + object: { + title: "Element (detailed)", + type: "object", + anyOf: [ + { + required: [ + "selector" + ] + }, + { + required: [ + "elementText" + ] + }, + { + required: [ + "elementId" + ] + }, + { + required: [ + "elementTestId" + ] + }, + { + required: [ + "elementClass" + ] + }, + { + required: [ + "elementAttribute" + ] + }, + { + required: [ + "elementAria" + ] + } + ], + properties: { + elementText: { + type: "string", + description: "Display text or regex pattern (enclosed in forward slashes) of the element. If combined with `selector`, the element must match both the text and the selector." + }, + selector: { + type: "string", + description: "Selector of the element. If combined with `elementText`, the element must match both the text and the selector." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + } + } + } + } + }, + examples: [ + { + source: "Table", + target: "#canvas" + }, + { + source: ".draggable-block", + target: ".drop-zone", + duration: 2e3 + }, + { + source: { + selector: ".widget", + elementText: "Data Table" + }, + target: { + selector: "#design-canvas" + }, + duration: 500 + }, + { + source: { + selector: ".draggable", + timeout: 1e4 + }, + target: { + elementText: "Drop Zone", + timeout: 5e3 + } + }, + { + source: "/Widget Item.*/", + target: "#canvas" + }, + { + source: { + selector: ".draggable", + elementText: "/Button [0-9]+/" + }, + target: { + elementText: "/Drop Zone.*/" + } + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "loadCookie", + type: "object", + required: [ + "loadCookie" + ], + properties: { + loadCookie: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "loadCookie", + description: "Load a specific cookie from a file or environment variable into the browser.", + anyOf: [ + { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + ], + components: { + schemas: { + string: { + type: "string", + title: "Cookie name or file path", + description: "Name of the specific cookie to load from default location, or file path to cookie file.", + pattern: "^[A-Za-z0-9_./\\-]+$", + transform: [ + "trim" + ] + }, + object: { + type: "object", + additionalProperties: false, + required: [ + "name" + ], + anyOf: [ + { + required: [ + "path" + ], + not: { + required: [ + "variable" + ] + } + }, + { + required: [ + "variable" + ], + not: { + anyOf: [ + { + required: [ + "path" + ] + }, + { + required: [ + "directory" + ] + } + ] + } + } + ], + properties: { + $schema: { + description: "Optional self-describing schema URI for linters", + type: "string", + format: "uri-reference" + }, + name: { + type: "string", + title: "Cookie name", + description: "Name of the specific cookie to load.", + pattern: "^[A-Za-z0-9_.-]+$", + transform: [ + "trim" + ] + }, + variable: { + type: "string", + title: "Environment variable name", + description: "Environment variable name containing the cookie as JSON string.", + pattern: "^[A-Za-z_][A-Za-z0-9_]*$", + transform: [ + "trim" + ] + }, + path: { + type: "string", + title: "Cookie file path", + description: "File path to cookie file, relative to directory. Supports Netscape cookie format.", + transform: [ + "trim" + ] + }, + directory: { + type: "string", + title: "Directory path", + description: "Directory containing the cookie file.", + transform: [ + "trim" + ] + }, + domain: { + type: "string", + title: "Cookie domain", + description: "Specific domain to filter the cookie by when loading from multi-cookie file (optional).", + transform: [ + "trim" + ] + } + }, + title: "Load cookie (detailed)" + } + } + }, + examples: [ + "session_token", + "./test-data/auth-session.txt", + "test_env_cookie", + { + name: "auth_cookie", + variable: "AUTH_COOKIE" + }, + { + name: "test_cookie", + path: "test-cookie.txt" + }, + { + name: "session_token", + path: "session-token.txt", + directory: "./test-data" + }, + { + name: "user_session", + path: "saved-cookies.txt", + domain: "app.example.com" + } + ] + } + } + } + ] + }, + { + allOf: [ + { + type: "object", + dynamicDefaults: { + stepId: "uuid" + }, + properties: { + $schema: { + description: "JSON Schema for this object.", + type: "string", + enum: [ + "https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json" + ] + }, + stepId: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + unsafe: { + type: "boolean", + description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.", + default: false + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Outputs (step)" + }, + variables: { + type: "object", + description: "Environment variables to set from user-defined expressions.", + default: {}, + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + title: "Variables (step)" + }, + breakpoint: { + type: "boolean", + description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.", + default: false + } + }, + title: "Common" + }, + { + title: "wait", + type: "object", + required: [ + "wait" + ], + properties: { + wait: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + } + } + } + ] + } + ], + examples: [ + { + stepId: "uuid", + description: "Description of the step.", + checkLink: "https://www.google.com", + outputs: { + outputKey: "outputValue" + }, + variables: { + variableKey: "variableValue" + } + }, + { + checkLink: "https://www.google.com" + }, + { + stepId: "path-only", + checkLink: "/search" + }, + { + stepId: "status-code", + checkLink: { + url: "https://www.google.com", + statusCodes: [ + 200 + ] + } + }, + { + goTo: { + url: "https://www.google.com" + } + }, + { + goTo: "https://www.google.com" + }, + { + wait: 5e3 + }, + { + runCode: { + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + stdio: "Hello from Python!", + path: "python-output.txt", + directory: "output", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + stopRecord: true + }, + { + screenshot: true + }, + { + screenshot: "image.png" + }, + { + screenshot: "static/images/image.png" + }, + { + screenshot: "/User/manny/projects/doc-detective/static/images/image.png" + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: "#elementToScreenshot" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation" + } + }, + { + screenshot: { + path: "image.png", + directory: "static/images", + maxVariation: 0.1, + overwrite: "aboveVariation", + crop: { + selector: "#elementToScreenshot", + elementText: "Element text", + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + } + }, + { + record: true + }, + { + record: "video.mp4" + }, + { + record: "static/media/video.mp4" + }, + { + record: "/User/manny/projects/doc-detective/static/media/video.mp4" + }, + { + record: { + path: "video.mp4", + directory: "static/media", + overwrite: true + } + }, + { + loadVariables: "variables.env" + }, + { + saveCookie: "session_token" + }, + { + saveCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data", + overwrite: true + } + }, + { + loadCookie: "session_token" + }, + { + loadCookie: { + name: "auth_cookie", + path: "auth-session.txt", + directory: "./test-data" + } + }, + { + find: "Find me!" + }, + { + find: { + selector: "[title=Search]" + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: "shorthair cat" + } + }, + { + find: { + selector: "[title=Search]", + click: { + button: "right" + } + } + }, + { + find: { + selector: "[title=Search]", + timeout: 1e4, + elementText: "Search", + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ], + inputDelay: 100 + } + } + }, + { + click: true + }, + { + click: "right" + }, + { + click: { + button: "left", + elementText: "Element text" + } + }, + { + click: { + selector: "#elementToScreenshot", + elementText: "Element text", + button: "middle" + } + }, + { + httpRequest: "https://reqres.in/api/users" + }, + { + httpRequest: { + url: "https://reqres.in/api/users" + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users/2", + method: "put", + request: { + body: { + name: "morpheus", + job: "zion resident" + } + } + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ] + } + }, + { + httpRequest: { + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + request: { + body: { + field: "value" + }, + headers: { + header: "value" + }, + parameters: { + param: "value" + } + }, + response: { + body: { + field: "value" + }, + headers: { + header: "value" + } + }, + statusCodes: [ + 200 + ] + } + }, + { + httpRequest: { + url: "https://reqres.in/api/users", + method: "post", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + path: "response.json", + directory: "media", + maxVariation: 0.05, + overwrite: "aboveVariation" + } + }, + { + httpRequest: { + openApi: "getUserById" + } + }, + { + httpRequest: { + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + request: { + parameters: { + id: 123 + } + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + } + }, + { + httpRequest: { + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + headers: { + Authorization: "Bearer $TOKEN" + } + } + } + }, + { + stepId: "breakpoint-example", + description: "Step with breakpoint enabled", + goTo: "https://www.example.com", + breakpoint: true + }, + { + checkLink: "https://www.google.com", + breakpoint: false + }, + { + dragAndDrop: { + source: { + selector: "#sourceElement" + }, + target: { + selector: "#targetElement" + } + } + } + ] + } + } + }, + title: "Resolved context" + } + } + }, + dynamicDefaults: { + testId: "uuid" + }, + anyOf: [ + { + required: [ + "steps" + ] + }, + { + required: [ + "contexts" + ] + } + ], + additionalProperties: false, + components: { + schemas: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI description and configuration.", + additionalProperties: false, + anyOf: [ + { + required: [ + "descriptionPath" + ] + }, + { + required: [ + "operationId" + ] + } + ], + properties: { + name: { + type: "string", + description: "Name of the OpenAPI description, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + }, + definition: { + type: "object", + readOnly: true, + description: "OpenAPI definition object loaded from the `descriptionPath`. This is a resolved version of the OpenAPI description and should not be user-defined.", + additionalProperties: true, + title: "OpenAPI definition" + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI description." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI description. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI description as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI description as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI description. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + headers: { + type: "object", + description: "Request headers to add to requests. For example, to set `Authorization` headers for all requests from the specified OpenAPI document. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + }, + title: "OpenAPI request headers" + } + }, + components: { + schemas: { + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI description." + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + }, + { + name: "Reqres", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201 + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none" + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true + }, + { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both", + exampleKey: "example1", + statusCode: 201, + validateAgainstSchema: "none", + mockResponse: true, + headers: { + Authorization: "Bearer 12345" + } + } + ] + }, + { + type: "object", + not: { + required: [ + "operationId" + ] + }, + required: [ + "name", + "descriptionPath" + ], + title: "OpenAPI description (test)" + } + ] + } + } + } + }, + examples: [ + { + steps: [ + { + checkLink: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + click: true, + type: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + } + ] + }, + { + testId: "Do all the things! - Test", + description: "This test includes every property across all actions.", + before: "setup.json", + after: "cleanup.json", + runOn: [ + { + platforms: [ + "linux" + ], + browsers: { + name: "firefox", + window: {}, + viewport: {} + } + } + ], + steps: [ + { + loadVariables: ".env" + }, + { + runShell: { + command: "echo", + args: [ + "$USER" + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + checkLink: { + url: "https://www.duckduckgo.com" + } + }, + { + httpRequest: { + method: "post", + url: "https://reqres.in/api/users", + request: { + body: { + name: "morpheus", + job: "leader" + } + }, + response: { + body: { + name: "morpheus", + job: "leader" + } + }, + statusCodes: [ + 200, + 201 + ], + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + }, + { + goTo: { + url: "https://www.duckduckgo.com" + } + }, + { + find: { + selector: "[title=Search]", + elementText: "Search", + timeout: 1e4, + moveTo: true, + click: true, + type: { + keys: [ + "shorthair cat" + ] + } + }, + variables: {} + }, + { + type: { + keys: [ + "$ENTER$" + ] + } + }, + { + screenshot: { + maxVariation: 0, + overwrite: "aboveVariation" + } + } + ], + detectSteps: true + }, + { + testId: "c61b02e8-7485-44d3-8065-f873673379c6", + openApi: [ + { + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "", + name: "Acme" + } + ], + steps: [ + { + httpRequest: { + openApi: { + operationId: "getUserById", + validateAgainstSchema: "both", + useExample: "none", + exampleKey: "" + }, + request: { + parameters: { + id: 123 + } + }, + response: {}, + maxVariation: 0, + overwrite: "aboveVariation" + }, + variables: {} + } + ], + detectSteps: true + } + ] + }, + type_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "typeKeys", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's keyword. For example, to type the Escape key, enter `$ESCAPE$`.", + anyOf: [ + { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + ], + components: { + schemas: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + object: { + title: "Type keys (detailed)", + type: "object", + properties: { + keys: { + title: "Type keys (simple)", + description: "Sequence of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + inputDelay: { + type: "number", + description: "Delay in milliseconds between each key press during a recording", + default: 100 + }, + selector: { + type: "string", + description: "Selector for the element to type into. If not specified, the typing occurs in the active element." + }, + elementText: { + type: "string", + description: "Display text of the element to type into. If combined with other element finding fields, the element must match all specified criteria." + }, + elementId: { + type: "string", + description: "ID attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementTestId: { + type: "string", + description: "data-testid attribute of the element to find. Supports exact match or regex pattern using /pattern/ syntax." + }, + elementClass: { + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + type: "string" + } + } + ], + description: "Class or array of classes that the element must have. Each class supports exact match or regex pattern using /pattern/ syntax. Element must have all specified classes." + }, + elementAttribute: { + type: "object", + description: "Object of attribute key-value pairs that the element must have. Values can be strings (supporting /pattern/ regex), numbers, or booleans. Boolean true matches attribute presence, false matches absence.", + additionalProperties: { + anyOf: [ + { + type: "string" + }, + { + type: "number" + }, + { + type: "boolean" + } + ] + } + }, + elementAria: { + type: "string", + description: "Computed accessible name of the element per ARIA specification. Supports exact match or regex pattern using /pattern/ syntax." + } + }, + required: [ + "keys" + ], + additionalProperties: false + } + } + }, + examples: [ + "kittens", + [ + "$ENTER$" + ], + [ + "kittens", + "$ENTER$" + ], + { + keys: "kittens" + }, + { + keys: [ + "$ENTER$" + ] + }, + { + keys: [ + "kittens", + "$ENTER$" + ], + inputDelay: 500 + } + ] + }, + wait_v3: { + $schema: "http://json-schema.org/draft-07/schema#", + title: "wait", + description: "Pause (in milliseconds) before performing the next action.", + default: 5e3, + anyOf: [ + { + type: "number", + title: "Wait (simple)" + }, + { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + { + type: "boolean", + title: "Wait (boolean)" + } + ], + components: { + schemas: { + string: { + title: "Wait (environment variable)", + type: "string", + pattern: "(\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + } + } + }, + examples: [ + 5e3, + "$WAIT_DURATION", + true + ] + }, + checkLink_v2: { + title: "checkLink", + type: "object", + description: "Check if a URL returns an acceptable status code from a GET request.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "checkLink", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to check.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201, + 202 + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "checkLink", + url: "https://www.google.com" + }, + { + action: "checkLink", + url: "https://www.google.com", + statusCodes: [ + 200 + ] + }, + { + action: "checkLink", + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + }, + config_v2: { + title: "config", + description: "Configuration options for Doc Detective operations.", + type: "object", + additionalProperties: false, + properties: { + defaultCommand: { + description: "Default command to run when no command is specified.", + type: "string", + enum: [ + "runTests", + "runCoverage" + ] + }, + input: { + default: ".", + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + output: { + default: ".", + description: "Path of the of the file or directory in which to store the output of Doc Detective commands. If a file path is specified, the output is written to that file. If a file of that name already exists, Doc Detective creates appends an integer to the result file name. If a directory path is specified, the output file name is dependent on the command being run.", + type: "string" + }, + recursive: { + default: true, + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specificaions and source files.", + type: "boolean" + }, + relativePathBase: { + description: "Whether paths should be interpreted as relative to the current working directory (`cwd`) or to the file in which they're specified (`file`).", + type: "string", + enum: [ + "cwd", + "file" + ], + default: "cwd" + }, + envVariables: { + description: "Path to a `.env` file to load before performing a Doc Detective operation.", + type: "string" + }, + runTests: { + type: "object", + additionalProperties: false, + description: "Options for running tests. When running tests, values set here override general configuration options.", + properties: { + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + output: { + description: "Path of the of the file or directory in which to store the output of Doc Detective commands. If a file path is specified, the output is written to that file. If a file of that name already exists, Doc Detective creates appends an integer to the result file name. If a directory path is specified, the output file name is dependent on the command being run.", + type: "string", + default: "." + }, + setup: { + description: "Path(s) to test specifications to perform before those specified by `input`. Useful for setting up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + cleanup: { + description: "Path(s) to test specifications to perform after those specified by `input`. Useful for cleaning up testing environments.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specificaions and source files.", + type: "boolean" + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex.", + default: false + }, + mediaDirectory: { + description: "DEPRECATED.", + type: "string", + default: "." + }, + downloadDirectory: { + description: "Path of the directory in which to store downloaded files.", + type: "string", + default: "." + }, + contexts: { + type: "array", + description: "Application/platform sets to run tests in. If no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it.", + items: { + oneOf: [ + { + title: "context", + type: "object", + description: "An application and supported platforms.\n\nIf no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For browsers, context priority is Firefox > Chrome > Chromium.", + properties: { + app: { + type: "object", + description: "The application to run.", + additionalProperties: false, + required: [ + "name" + ], + properties: { + name: { + type: "string", + description: "Name of the application.", + enum: [ + "chrome", + "firefox", + "safari", + "edge" + ] + }, + path: { + type: "string", + description: "Absolute path or command for the application. If not specified, defaults to typical install paths per platform. If specified but the path is invalid, the context is skipped." + }, + options: { + type: "object", + description: "Options to pass to the app. Only works when `name` is `firefox` or `chrome`.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the window in pixels." + }, + height: { + type: "integer", + description: "Height of the window in pixels." + }, + viewport_height: { + type: "integer", + description: "Height of the viewport in pixels. Overrides `height`." + }, + viewport_width: { + type: "integer", + description: "Width of the viewport in pixels. Overrides `width`." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode. Not supported by Safari." + }, + driverPath: { + type: "string", + description: "Path to the browser driver. If not specified, defaults to internally managed dependencies." + } + } + } + } + }, + platforms: { + description: "Supported platforms for the application.", + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + }, + required: [ + "app", + "platforms" + ], + additionalProperties: false, + examples: [ + { + app: { + name: "chrome" + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "chrome", + options: { + viewport_width: 800, + viewport_height: 600 + } + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "firefox", + options: { + width: 800, + height: 600, + headless: false, + driverPath: "/usr/bin/geckodriver" + } + }, + platforms: [ + "linux", + "windows", + "mac" + ] + }, + { + app: { + name: "safari" + }, + platforms: [ + "mac" + ] + }, + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ] + } + ] + }, + default: [ + { + app: { + name: "firefox", + options: { + width: 1200, + height: 800, + headless: true + } + }, + platforms: [ + "linux", + "mac", + "windows" + ] + } + ] + } + } + }, + runCoverage: { + description: "Options for performing test coverage analysis on documentation source files. When performing coveration analysis, values set here override general configuration options.", + type: "object", + additionalProperties: false, + properties: { + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + output: { + description: "Path of the of the file or directory in which to store the output of Doc Detective commands. If a file path is specified, the output is written to that file. If a file of that name already exists, Doc Detective creates appends an integer to the result file name. If a directory path is specified, the output file name is dependent on the command being run.", + type: "string", + default: "." + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specificaions and source files.", + type: "boolean" + }, + markup: { + default: [ + "onscreenText", + "emphasis", + "image", + "hyperlink", + "codeInline", + "codeBlock", + "interaction" + ], + description: "Markup types to include when performing this operation. If no markup types are specified, the operation includes all markup types as defined in `fileTypes`.", + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + } + }, + suggestTests: { + description: "Options for suggesting tests based on documentation source files. When suggesting tests, values set here override general condiguration options.", + type: "object", + additionalProperties: false, + properties: { + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + output: { + description: "Path of the of the file or directory in which to store the output of Doc Detective commands. If a file path is specified, the output is written to that file. If a file of that name already exists, Doc Detective creates appends an integer to the result file name. If a directory path is specified, the output file name is dependent on the command being run.", + type: "string", + default: "." + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specificaions and source files.", + type: "boolean" + }, + markup: { + default: [ + "onscreenText", + "emphasis", + "image", + "hyperlink", + "codeInline", + "codeBlock", + "interaction" + ], + description: "Markup types to include when performing this operation. If no markup types are specified, the operation includes all markup types as defined in `fileTypes`.", + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + } + }, + fileTypes: { + description: "Information on supported file types and how to parse the markup within them.", + type: "array", + items: { + oneOf: [ + { + type: "object", + additionalProperties: false, + properties: { + name: { + description: "Name of the file type.", + type: "string" + }, + extensions: { + description: "File extensions to support with this configuration.", + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + }, + testStartStatementOpen: { + description: "Opening of an in-document test start statement.", + type: "string" + }, + testStartStatementClose: { + description: "Close of an in-document test start statement.", + type: "string" + }, + testIgnoreStatement: { + description: "Text for an in-document test ignore statement.", + type: "string" + }, + testEndStatement: { + description: "Text for an in-document test end statement.", + type: "string" + }, + stepStatementOpen: { + description: "Opening of an in-document step statement.", + type: "string" + }, + stepStatementClose: { + description: "Close of an in-document step statement.", + type: "string" + }, + markup: { + description: "Markup types and associated regex patterns to find in documentation source files.", + type: "array", + items: { + oneOf: [ + { + type: "object", + additionalProperties: false, + properties: { + name: { + description: "Name of the markup type.", + type: "string" + }, + regex: { + description: "Regex patterns to find the markup type in documentation source files.", + type: "array", + minItems: 1, + items: { + oneOf: [ + { + type: "string" + } + ] + } + }, + actions: { + description: "Actions that apply to the markup type.", + type: "array", + items: { + oneOf: [ + { + type: "string", + enum: [ + "checkLink", + "find", + "goTo", + "httpRequest", + "runShell", + "saveScreenshot", + "setVariables", + "startRecording", + "stopRecording", + "typeKeys", + "wait" + ] + }, + { + type: "object", + additionalProperties: false, + properties: { + name: { + description: "Name of the action.", + type: "string", + enum: [ + "checkLink", + "find", + "goTo", + "httpRequest", + "runShell", + "saveScreenshot", + "setVariables", + "startRecording", + "stopRecording", + "typeKeys", + "wait" + ] + }, + params: { + description: "Parameters for the action.", + type: "object", + additionalProperties: true + } + }, + required: [ + "name" + ] + }, + { + title: "checkLink", + type: "object", + description: "Check if a URL returns an acceptable status code from a GET request.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "checkLink", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to check.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201, + 202 + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "checkLink", + url: "https://www.google.com" + }, + { + action: "checkLink", + url: "https://www.google.com", + statusCodes: [ + 200 + ] + }, + { + action: "checkLink", + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + }, + { + title: "find", + type: "object", + description: "Check if an element exists with the specified CSS selector.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "find", + description: "Action to perform." + }, + selector: { + description: "Selector that uniquely identifies the element.", + type: "string" + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + matchText: { + type: "string", + description: "Text that the element should contain. If the element doesn't contain the text, the step fails. Accepts both strings an regular expressions. To use a regular expression, the expression should start and end with a `/`. For example, `/search/`." + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view. Only runs the if the test is being recorded.", + oneOf: [ + { + type: "boolean" + } + ], + default: false + }, + click: { + description: "Click the element.", + oneOf: [ + { + type: "boolean", + default: false + }, + { + type: "object", + additionalProperties: false, + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + } + } + ] + }, + typeKeys: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`typeKeys`](typeKeys). To type in the element, make the element active with the `click` parameter.", + oneOf: [ + { + type: "string" + }, + { + type: "object", + additionalProperties: false, + properties: { + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + } + } + ] + }, + setVariables: { + type: "array", + description: "Extract environment variables from the element's text.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the element's text.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + required: [ + "action", + "selector" + ], + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + examples: [ + { + action: "find", + selector: "[title=Search]" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: "shorthair cat" + }, + { + action: "find", + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ], + delay: 100 + } + }, + { + action: "find", + selector: "[title=ResultsCount]", + setVariables: [ + { + name: "resultsCount", + regex: ".*" + } + ] + } + ] + }, + { + title: "goTo", + type: "object", + description: "Navigate to a specified URL.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "goTo", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to navigate to.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!", + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516756", + description: "This is a test!", + action: "goTo", + url: "/search", + origin: "https://www.google.com" + } + ] + }, + { + title: "httpRequest", + type: "object", + description: "Perform a generic HTTP request, for example to an API.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "httpRequest", + description: "Aciton to perform." + }, + url: { + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + required: [ + "operationId" + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + requestHeaders: { + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + responseHeaders: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + requestParams: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseParams: { + description: "DEPRECATED.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + requestData: { + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseData: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in `responseData`.", + default: true + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + envsFromResponseData: { + description: "Environment variables to set based on response variables, as an object of the environment variable name and the jq filter applied to the response data to identify the variable's value.", + type: "array", + default: [], + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + jqFilter: { + description: "jq filter to apply to the response data. If the filter doesn't return a value, the environment variable isn't set.", + type: "string" + } + }, + required: [ + "name", + "jqFilter" + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "httpRequest", + url: "https://reqres.in/api/users" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users/2", + method: "put", + requestData: { + name: "morpheus", + job: "zion resident" + } + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "httpRequest", + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + requestHeaders: { + header: "value" + }, + requestParams: { + param: "value" + }, + requestData: { + field: "value" + }, + responseHeaders: { + header: "value" + }, + responseData: { + field: "value" + }, + statusCodes: [ + 200 + ] + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ], + savePath: "response.json", + saveDirectory: "media", + maxVariation: 5, + overwrite: "byVariation" + }, + { + action: "httpRequest", + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + requestHeaders: { + Authorization: "Bearer $TOKEN" + } + } + } + ] + }, + { + title: "runShell", + type: "object", + description: "Perform a native shell command.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runShell", + description: "The action to perform." + }, + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "command" + ], + examples: [ + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "hello-world" + ], + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!" + }, + { + action: "runShell", + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runShell", + command: "false", + exitCodes: [ + 1 + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + output: "/.*?/", + setVariables: [ + { + name: "TEST", + regex: ".*" + } + ] + }, + { + action: "runShell", + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Docker!", + savePath: "docker-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + { + title: "saveScreenshot", + type: "object", + description: "Takes a screenshot in PNG format.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "saveScreenshot", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the PNG file, relative to `directory`. If not specified, the file name is the ID of the step.", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the exisitng screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 5, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `byVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + crop: { + type: "object", + description: "Crops the screenshot.", + properties: { + selector: { + type: "string", + description: "Selector of the element to crop the image to." + }, + padding: { + oneOf: [ + { + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + } + } + ] + } + }, + required: [ + "selector" + ], + additionalProperties: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "saveScreenshot" + }, + { + action: "saveScreenshot", + path: "results.png" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + maxVariation: 10, + overwrite: "byVariation" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element" + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: 10 + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: { + top: 10, + right: 20, + bottom: 30, + left: 40 + } + } + } + ] + }, + { + title: "setVariables", + type: "object", + description: "Load environment variables from a `.env` file.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "setVariables", + description: "Action to perform." + }, + path: { + type: "string", + description: "Path to the `.env` file." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "path" + ], + additionalProperties: false, + examples: [ + { + action: "setVariables", + path: ".env" + } + ] + }, + { + title: "startRecording", + type: "object", + description: "Start recording the current browser viewport. Must be followed by a `stopRecording` action. Only runs when the context `app` is `chrome` and `headless` is `false`. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "startRecording", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the file. Attempts to create the directory if it doesn't exist." + }, + overwrite: { + type: "boolean", + description: "If `true`, overwrites the existing file at `path` if it exists.", + default: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "startRecording" + }, + { + action: "startRecording", + path: "results.mp4" + }, + { + action: "startRecording", + path: "results.mp4", + directory: "static/media", + overwrite: true + } + ] + }, + { + title: "stopRecording", + type: "object", + description: "Stop the current recording.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "stopRecording", + description: "The action to perform." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "stopRecording" + } + ] + }, + { + title: "typeKeys", + type: "object", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's enum. For example, to type the Escape key, enter `$ESCAPE$`.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "typeKeys", + description: "The action to perform." + }, + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "keys" + ], + additionalProperties: false, + examples: [ + { + action: "typeKeys", + keys: "kittens" + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "typeKeys", + keys: [ + "kittens", + "$ENTER$" + ], + delay: 500 + } + ] + }, + { + title: "wait", + type: "object", + description: "Pause before performing the next action.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "wait", + description: "The action to perform." + }, + duration: { + type: "number", + description: "Milliseconds to wait.", + default: 5e3 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "wait" + }, + { + action: "wait", + duration: 5e3 + } + ] + } + ] + } + } + }, + required: [ + "name", + "regex" + ] + } + ] + } + } + }, + required: [ + "extensions", + "testStartStatementOpen", + "testStartStatementClose", + "testIgnoreStatement", + "testEndStatement", + "stepStatementOpen", + "stepStatementClose", + "markup" + ] + } + ] + }, + default: [ + { + name: "Markdown", + extensions: [ + ".md", + ".markdown", + ".mdx" + ], + testStartStatementOpen: "[comment]: # (test start", + testStartStatementClose: ")", + testIgnoreStatement: "[comment]: # (test ignore)", + testEndStatement: "[comment]: # (test end)", + stepStatementOpen: "[comment]: # (step", + stepStatementClose: ")", + markup: [ + { + name: "onscreenText", + regex: [ + "\\*\\*.+?\\*\\*" + ], + actions: [ + "find" + ] + }, + { + name: "emphasis", + regex: [ + "(?", + testIgnoreStatement: "", + testEndStatement: "", + stepStatementOpen: "", + markup: [] + } + ] + }, + integrations: { + description: "Options for connecting to external services.", + type: "object", + additionalProperties: false, + properties: { + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + properties: { + operationId: { + type: "null", + $commment: "Only allow operationId at the step level." + } + }, + required: [ + "name", + "descriptionPath" + ] + } + ] + } + } + } + }, + telemetry: { + description: "Options around sending telemetry for Doc Detective usage.", + type: "object", + additionalProperties: false, + properties: { + send: { + description: "If `true`, sends Doc Detective telemetry.", + type: "boolean", + default: true + }, + userId: { + description: "Identifier for the organization, group, or individual running Doc Detective.", + type: "string" + } + }, + required: [ + "send" + ], + default: { + send: true + } + }, + logLevel: { + description: "Amount of detail to output when performing an operation.", + type: "string", + enum: [ + "silent", + "error", + "warning", + "info", + "debug" + ], + default: "info" + } + }, + definitions: { + input: { + description: "Path(s) to test specifications and documentation source files. May be paths to specific files or to directories to scan for files.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + anyOf: [ + { + type: "string" + } + ] + } + } + ] + }, + recursive: { + description: "If `true` searches `input`, `setup`, and `cleanup` paths recursively for test specificaions and source files.", + type: "boolean" + }, + output: { + description: "Path of the of the file or directory in which to store the output of Doc Detective commands. If a file path is specified, the output is written to that file. If a file of that name already exists, Doc Detective creates appends an integer to the result file name. If a directory path is specified, the output file name is dependent on the command being run.", + type: "string", + default: "." + }, + markupToInclude: { + description: "Markup types to include when performing this operation. If no markup types are specified, the operation includes all markup types as defined in `fileTypes`.", + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + }, + examples: [ + {}, + { + input: ".", + output: "." + }, + { + defaultCommand: "runTests", + envVariables: "", + input: ".", + output: ".", + recursive: true, + logLevel: "info", + runTests: { + input: ".", + output: ".", + setup: "", + cleanup: "", + recursive: true, + downloadDirectory: ".", + contexts: [ + { + app: { + name: "firefox", + path: "" + }, + platforms: [ + "linux", + "mac", + "windows" + ] + } + ] + } + }, + { + integrations: { + openApi: [ + { + name: "Acme", + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com", + mockResponse: true + } + ] + } + }, + { + envVariables: "", + input: ".", + output: ".", + recursive: true, + logLevel: "info", + runTests: { + input: ".", + output: ".", + setup: "", + cleanup: "", + recursive: true, + downloadDirectory: ".", + contexts: [ + { + app: { + name: "firefox", + path: "" + }, + platforms: [ + "linux", + "mac", + "windows" + ] + } + ] + }, + runCoverage: { + recursive: true, + input: ".", + output: ".", + markup: [] + }, + fileTypes: [ + { + name: "Markdown", + extensions: [ + ".md", + ".markdown", + ".mdx" + ], + testStartStatementOpen: "[comment]: # (test start", + testStartStatementClose: ")", + testIgnoreStatement: "[comment]: # (test ignore)", + testEndStatement: "[comment]: # (test end)", + stepStatementOpen: "[comment]: # (step", + stepStatementClose: ")", + markup: [ + { + name: "onscreenText", + regex: [ + "\\*\\*.+?\\*\\*" + ], + actions: [ + "find" + ] + }, + { + name: "emphasis", + regex: [ + "(?", + testIgnoreStatement: "", + testEndStatement: "", + stepStatementOpen: "", + markup: [] + } + ], + integrations: {}, + telemetry: { + send: true, + userId: "Doc Detective" + } + } + ] + }, + context_v2: { + title: "context", + type: "object", + description: "An application and supported platforms.\n\nIf no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For browsers, context priority is Firefox > Chrome > Chromium.", + properties: { + app: { + type: "object", + description: "The application to run.", + additionalProperties: false, + required: [ + "name" + ], + properties: { + name: { + type: "string", + description: "Name of the application.", + enum: [ + "chrome", + "firefox", + "safari", + "edge" + ] + }, + path: { + type: "string", + description: "Absolute path or command for the application. If not specified, defaults to typical install paths per platform. If specified but the path is invalid, the context is skipped." + }, + options: { + type: "object", + description: "Options to pass to the app. Only works when `name` is `firefox` or `chrome`.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the window in pixels." + }, + height: { + type: "integer", + description: "Height of the window in pixels." + }, + viewport_height: { + type: "integer", + description: "Height of the viewport in pixels. Overrides `height`." + }, + viewport_width: { + type: "integer", + description: "Width of the viewport in pixels. Overrides `width`." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode. Not supported by Safari." + }, + driverPath: { + type: "string", + description: "Path to the browser driver. If not specified, defaults to internally managed dependencies." + } + } + } + } + }, + platforms: { + description: "Supported platforms for the application.", + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + }, + required: [ + "app", + "platforms" + ], + additionalProperties: false, + examples: [ + { + app: { + name: "chrome" + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "chrome", + options: { + viewport_width: 800, + viewport_height: 600 + } + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "firefox", + options: { + width: 800, + height: 600, + headless: false, + driverPath: "/usr/bin/geckodriver" + } + }, + platforms: [ + "linux", + "windows", + "mac" + ] + }, + { + app: { + name: "safari" + }, + platforms: [ + "mac" + ] + }, + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ] + }, + find_v2: { + title: "find", + type: "object", + description: "Check if an element exists with the specified CSS selector.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "find", + description: "Action to perform." + }, + selector: { + description: "Selector that uniquely identifies the element.", + type: "string" + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + matchText: { + type: "string", + description: "Text that the element should contain. If the element doesn't contain the text, the step fails. Accepts both strings an regular expressions. To use a regular expression, the expression should start and end with a `/`. For example, `/search/`." + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view. Only runs the if the test is being recorded.", + oneOf: [ + { + type: "boolean" + } + ], + default: false + }, + click: { + description: "Click the element.", + oneOf: [ + { + type: "boolean", + default: false + }, + { + type: "object", + additionalProperties: false, + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + } + } + ] + }, + typeKeys: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`typeKeys`](typeKeys). To type in the element, make the element active with the `click` parameter.", + oneOf: [ + { + type: "string" + }, + { + type: "object", + additionalProperties: false, + properties: { + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + } + } + ] + }, + setVariables: { + type: "array", + description: "Extract environment variables from the element's text.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the element's text.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + required: [ + "action", + "selector" + ], + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + examples: [ + { + action: "find", + selector: "[title=Search]" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: "shorthair cat" + }, + { + action: "find", + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ], + delay: 100 + } + }, + { + action: "find", + selector: "[title=ResultsCount]", + setVariables: [ + { + name: "resultsCount", + regex: ".*" + } + ] + } + ] + }, + goTo_v2: { + title: "goTo", + type: "object", + description: "Navigate to a specified URL.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "goTo", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to navigate to.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!", + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516756", + description: "This is a test!", + action: "goTo", + url: "/search", + origin: "https://www.google.com" + } + ] + }, + httpRequest_v2: { + title: "httpRequest", + type: "object", + description: "Perform a generic HTTP request, for example to an API.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "httpRequest", + description: "Aciton to perform." + }, + url: { + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + required: [ + "operationId" + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + requestHeaders: { + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + responseHeaders: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + requestParams: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseParams: { + description: "DEPRECATED.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + requestData: { + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseData: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in `responseData`.", + default: true + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + envsFromResponseData: { + description: "Environment variables to set based on response variables, as an object of the environment variable name and the jq filter applied to the response data to identify the variable's value.", + type: "array", + default: [], + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + jqFilter: { + description: "jq filter to apply to the response data. If the filter doesn't return a value, the environment variable isn't set.", + type: "string" + } + }, + required: [ + "name", + "jqFilter" + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "httpRequest", + url: "https://reqres.in/api/users" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users/2", + method: "put", + requestData: { + name: "morpheus", + job: "zion resident" + } + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "httpRequest", + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + requestHeaders: { + header: "value" + }, + requestParams: { + param: "value" + }, + requestData: { + field: "value" + }, + responseHeaders: { + header: "value" + }, + responseData: { + field: "value" + }, + statusCodes: [ + 200 + ] + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ], + savePath: "response.json", + saveDirectory: "media", + maxVariation: 5, + overwrite: "byVariation" + }, + { + action: "httpRequest", + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + requestHeaders: { + Authorization: "Bearer $TOKEN" + } + } + } + ] + }, + moveTo_v2: { + title: "moveTo", + type: "object", + description: "Move the mouse to a specific location.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "moveTo", + description: "The action to perform." + }, + selector: { + description: "Selector for the element to move to.", + type: "string" + }, + alignment: { + description: "Alignment of the element to move to.", + type: "string", + enum: [ + "top", + "bottom", + "left", + "right", + "center" + ], + default: "center" + }, + offset: { + description: "Offset from the element to move to.", + type: "object", + properties: { + x: { + description: "Offset from the element to move to in x direction. Negative values move left, positive values move right.", + type: "number", + default: 0 + }, + y: { + description: "Offset from the element to move to in y direction. Negative values move up, positive values move down.", + type: "number", + default: 0 + } + }, + default: {}, + additionalProperties: false + }, + duration: { + description: "Duration of the move in milliseconds.", + type: "number", + minimum: 0, + default: 500 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "selector" + ], + additionalProperties: false, + examples: [ + { + action: "moveTo", + selector: "#searchInput" + }, + { + action: "moveTo", + selector: "#searchInput", + alignment: "left" + }, + { + action: "moveTo", + selector: "#searchInput", + alignment: "left", + offset: { + x: 10, + y: 10 + } + }, + { + action: "moveTo", + selector: "#searchInput", + alignment: "left", + offset: { + x: 10, + y: 10 + }, + duration: 1e3 + } + ] + }, + openApi_v2: { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + runShell_v2: { + title: "runShell", + type: "object", + description: "Perform a native shell command.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runShell", + description: "The action to perform." + }, + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "command" + ], + examples: [ + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "hello-world" + ], + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!" + }, + { + action: "runShell", + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runShell", + command: "false", + exitCodes: [ + 1 + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + output: "/.*?/", + setVariables: [ + { + name: "TEST", + regex: ".*" + } + ] + }, + { + action: "runShell", + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Docker!", + savePath: "docker-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + runCode_v2: { + title: "runCode", + type: "object", + description: "Assemble and run code.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runCode", + description: "The action to perform." + }, + language: { + type: "string", + description: "Language of the code to run. If not specified, the code is run in the shell.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + type: "string", + description: "Code to run." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + properties: { + stdout: { + type: "string", + description: "Standard output of the command.", + readOnly: true + }, + stderr: { + type: "string", + description: "Standard error of the command.", + readOnly: true + }, + exitCode: { + type: "integer", + description: "Exit code of the command.", + readOnly: true + } + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "code", + "language" + ], + examples: [ + { + action: "runCode", + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + action: "runCode", + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runCode", + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + action: "runCode", + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Python!", + savePath: "python-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + saveScreenshot_v2: { + title: "saveScreenshot", + type: "object", + description: "Takes a screenshot in PNG format.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "saveScreenshot", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the PNG file, relative to `directory`. If not specified, the file name is the ID of the step.", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the exisitng screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 5, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `byVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + crop: { + type: "object", + description: "Crops the screenshot.", + properties: { + selector: { + type: "string", + description: "Selector of the element to crop the image to." + }, + padding: { + oneOf: [ + { + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + } + } + ] + } + }, + required: [ + "selector" + ], + additionalProperties: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "saveScreenshot" + }, + { + action: "saveScreenshot", + path: "results.png" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + maxVariation: 10, + overwrite: "byVariation" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element" + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: 10 + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: { + top: 10, + right: 20, + bottom: 30, + left: 40 + } + } + } + ] + }, + setVariables_v2: { + title: "setVariables", + type: "object", + description: "Load environment variables from a `.env` file.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "setVariables", + description: "Action to perform." + }, + path: { + type: "string", + description: "Path to the `.env` file." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "path" + ], + additionalProperties: false, + examples: [ + { + action: "setVariables", + path: ".env" + } + ] + }, + startRecording_v2: { + title: "startRecording", + type: "object", + description: "Start recording the current browser viewport. Must be followed by a `stopRecording` action. Only runs when the context `app` is `chrome` and `headless` is `false`. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "startRecording", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the file. Attempts to create the directory if it doesn't exist." + }, + overwrite: { + type: "boolean", + description: "If `true`, overwrites the existing file at `path` if it exists.", + default: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "startRecording" + }, + { + action: "startRecording", + path: "results.mp4" + }, + { + action: "startRecording", + path: "results.mp4", + directory: "static/media", + overwrite: true + } + ] + }, + stopRecording_v2: { + title: "stopRecording", + type: "object", + description: "Stop the current recording.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "stopRecording", + description: "The action to perform." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "stopRecording" + } + ] + }, + spec_v2: { + title: "specification", + type: "object", + properties: { + id: { + type: "string", + description: "Unique identifier for the test specification." + }, + description: { + type: "string", + description: "Description of the test specification." + }, + file: { + type: "string", + description: "Path to the file that the specification is associated with." + }, + contexts: { + type: "array", + description: "Application/platform sets to run tests in. Overrides `contexts` defined at the config-level.", + items: { + oneOf: [ + { + title: "context", + type: "object", + description: "An application and supported platforms.\n\nIf no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For browsers, context priority is Firefox > Chrome > Chromium.", + properties: { + app: { + type: "object", + description: "The application to run.", + additionalProperties: false, + required: [ + "name" + ], + properties: { + name: { + type: "string", + description: "Name of the application.", + enum: [ + "chrome", + "firefox", + "safari", + "edge" + ] + }, + path: { + type: "string", + description: "Absolute path or command for the application. If not specified, defaults to typical install paths per platform. If specified but the path is invalid, the context is skipped." + }, + options: { + type: "object", + description: "Options to pass to the app. Only works when `name` is `firefox` or `chrome`.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the window in pixels." + }, + height: { + type: "integer", + description: "Height of the window in pixels." + }, + viewport_height: { + type: "integer", + description: "Height of the viewport in pixels. Overrides `height`." + }, + viewport_width: { + type: "integer", + description: "Width of the viewport in pixels. Overrides `width`." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode. Not supported by Safari." + }, + driverPath: { + type: "string", + description: "Path to the browser driver. If not specified, defaults to internally managed dependencies." + } + } + } + } + }, + platforms: { + description: "Supported platforms for the application.", + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + }, + required: [ + "app", + "platforms" + ], + additionalProperties: false, + examples: [ + { + app: { + name: "chrome" + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "chrome", + options: { + viewport_width: 800, + viewport_height: 600 + } + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "firefox", + options: { + width: 800, + height: 600, + headless: false, + driverPath: "/usr/bin/geckodriver" + } + }, + platforms: [ + "linux", + "windows", + "mac" + ] + }, + { + app: { + name: "safari" + }, + platforms: [ + "mac" + ] + }, + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + properties: { + operationId: { + type: "null", + $commment: "Only allow operationId at the step level." + } + }, + required: [ + "name", + "descriptionPath" + ] + } + ] + } + }, + tests: { + description: "[Tests](test) to perform.", + type: "array", + minItems: 1, + items: { + oneOf: [ + { + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + id: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + file: { + type: "string", + description: "Path to the file that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex. Defaults to `true`." + }, + contexts: { + type: "array", + description: "Application/platform sets to run the test in. Overrides `contexts` defined at the config-level and spec-level.", + items: { + oneOf: [ + { + title: "context", + type: "object", + description: "An application and supported platforms.\n\nIf no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For browsers, context priority is Firefox > Chrome > Chromium.", + properties: { + app: { + type: "object", + description: "The application to run.", + additionalProperties: false, + required: [ + "name" + ], + properties: { + name: { + type: "string", + description: "Name of the application.", + enum: [ + "chrome", + "firefox", + "safari", + "edge" + ] + }, + path: { + type: "string", + description: "Absolute path or command for the application. If not specified, defaults to typical install paths per platform. If specified but the path is invalid, the context is skipped." + }, + options: { + type: "object", + description: "Options to pass to the app. Only works when `name` is `firefox` or `chrome`.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the window in pixels." + }, + height: { + type: "integer", + description: "Height of the window in pixels." + }, + viewport_height: { + type: "integer", + description: "Height of the viewport in pixels. Overrides `height`." + }, + viewport_width: { + type: "integer", + description: "Width of the viewport in pixels. Overrides `width`." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode. Not supported by Safari." + }, + driverPath: { + type: "string", + description: "Path to the browser driver. If not specified, defaults to internally managed dependencies." + } + } + } + } + }, + platforms: { + description: "Supported platforms for the application.", + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + }, + required: [ + "app", + "platforms" + ], + additionalProperties: false, + examples: [ + { + app: { + name: "chrome" + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "chrome", + options: { + viewport_width: 800, + viewport_height: 600 + } + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "firefox", + options: { + width: 800, + height: 600, + headless: false, + driverPath: "/usr/bin/geckodriver" + } + }, + platforms: [ + "linux", + "windows", + "mac" + ] + }, + { + app: { + name: "safari" + }, + platforms: [ + "mac" + ] + }, + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + properties: { + operationId: { + type: "null", + $commment: "Only allow operationId at the step level." + } + }, + required: [ + "name", + "descriptionPath" + ] + } + ] + } + }, + setup: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + cleanup: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Actions to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + title: "checkLink", + type: "object", + description: "Check if a URL returns an acceptable status code from a GET request.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "checkLink", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to check.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201, + 202 + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "checkLink", + url: "https://www.google.com" + }, + { + action: "checkLink", + url: "https://www.google.com", + statusCodes: [ + 200 + ] + }, + { + action: "checkLink", + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + }, + { + title: "goTo", + type: "object", + description: "Navigate to a specified URL.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "goTo", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to navigate to.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!", + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516756", + description: "This is a test!", + action: "goTo", + url: "/search", + origin: "https://www.google.com" + } + ] + }, + { + title: "httpRequest", + type: "object", + description: "Perform a generic HTTP request, for example to an API.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "httpRequest", + description: "Aciton to perform." + }, + url: { + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + required: [ + "operationId" + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + requestHeaders: { + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + responseHeaders: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + requestParams: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseParams: { + description: "DEPRECATED.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + requestData: { + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseData: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in `responseData`.", + default: true + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + envsFromResponseData: { + description: "Environment variables to set based on response variables, as an object of the environment variable name and the jq filter applied to the response data to identify the variable's value.", + type: "array", + default: [], + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + jqFilter: { + description: "jq filter to apply to the response data. If the filter doesn't return a value, the environment variable isn't set.", + type: "string" + } + }, + required: [ + "name", + "jqFilter" + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "httpRequest", + url: "https://reqres.in/api/users" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users/2", + method: "put", + requestData: { + name: "morpheus", + job: "zion resident" + } + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "httpRequest", + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + requestHeaders: { + header: "value" + }, + requestParams: { + param: "value" + }, + requestData: { + field: "value" + }, + responseHeaders: { + header: "value" + }, + responseData: { + field: "value" + }, + statusCodes: [ + 200 + ] + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ], + savePath: "response.json", + saveDirectory: "media", + maxVariation: 5, + overwrite: "byVariation" + }, + { + action: "httpRequest", + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + requestHeaders: { + Authorization: "Bearer $TOKEN" + } + } + } + ] + }, + { + title: "runCode", + type: "object", + description: "Assemble and run code.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runCode", + description: "The action to perform." + }, + language: { + type: "string", + description: "Language of the code to run. If not specified, the code is run in the shell.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + type: "string", + description: "Code to run." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + properties: { + stdout: { + type: "string", + description: "Standard output of the command.", + readOnly: true + }, + stderr: { + type: "string", + description: "Standard error of the command.", + readOnly: true + }, + exitCode: { + type: "integer", + description: "Exit code of the command.", + readOnly: true + } + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "code", + "language" + ], + examples: [ + { + action: "runCode", + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + action: "runCode", + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runCode", + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + action: "runCode", + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Python!", + savePath: "python-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + { + title: "runShell", + type: "object", + description: "Perform a native shell command.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runShell", + description: "The action to perform." + }, + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "command" + ], + examples: [ + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "hello-world" + ], + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!" + }, + { + action: "runShell", + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runShell", + command: "false", + exitCodes: [ + 1 + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + output: "/.*?/", + setVariables: [ + { + name: "TEST", + regex: ".*" + } + ] + }, + { + action: "runShell", + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Docker!", + savePath: "docker-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + { + title: "saveScreenshot", + type: "object", + description: "Takes a screenshot in PNG format.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "saveScreenshot", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the PNG file, relative to `directory`. If not specified, the file name is the ID of the step.", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the exisitng screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 5, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `byVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + crop: { + type: "object", + description: "Crops the screenshot.", + properties: { + selector: { + type: "string", + description: "Selector of the element to crop the image to." + }, + padding: { + oneOf: [ + { + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + } + } + ] + } + }, + required: [ + "selector" + ], + additionalProperties: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "saveScreenshot" + }, + { + action: "saveScreenshot", + path: "results.png" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + maxVariation: 10, + overwrite: "byVariation" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element" + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: 10 + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: { + top: 10, + right: 20, + bottom: 30, + left: 40 + } + } + } + ] + }, + { + title: "setVariables", + type: "object", + description: "Load environment variables from a `.env` file.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "setVariables", + description: "Action to perform." + }, + path: { + type: "string", + description: "Path to the `.env` file." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "path" + ], + additionalProperties: false, + examples: [ + { + action: "setVariables", + path: ".env" + } + ] + }, + { + title: "startRecording", + type: "object", + description: "Start recording the current browser viewport. Must be followed by a `stopRecording` action. Only runs when the context `app` is `chrome` and `headless` is `false`. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "startRecording", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the file. Attempts to create the directory if it doesn't exist." + }, + overwrite: { + type: "boolean", + description: "If `true`, overwrites the existing file at `path` if it exists.", + default: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "startRecording" + }, + { + action: "startRecording", + path: "results.mp4" + }, + { + action: "startRecording", + path: "results.mp4", + directory: "static/media", + overwrite: true + } + ] + }, + { + title: "stopRecording", + type: "object", + description: "Stop the current recording.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "stopRecording", + description: "The action to perform." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "stopRecording" + } + ] + }, + { + title: "typeKeys", + type: "object", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's enum. For example, to type the Escape key, enter `$ESCAPE$`.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "typeKeys", + description: "The action to perform." + }, + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "keys" + ], + additionalProperties: false, + examples: [ + { + action: "typeKeys", + keys: "kittens" + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "typeKeys", + keys: [ + "kittens", + "$ENTER$" + ], + delay: 500 + } + ] + }, + { + title: "find", + type: "object", + description: "Check if an element exists with the specified CSS selector.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "find", + description: "Action to perform." + }, + selector: { + description: "Selector that uniquely identifies the element.", + type: "string" + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + matchText: { + type: "string", + description: "Text that the element should contain. If the element doesn't contain the text, the step fails. Accepts both strings an regular expressions. To use a regular expression, the expression should start and end with a `/`. For example, `/search/`." + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view. Only runs the if the test is being recorded.", + oneOf: [ + { + type: "boolean" + } + ], + default: false + }, + click: { + description: "Click the element.", + oneOf: [ + { + type: "boolean", + default: false + }, + { + type: "object", + additionalProperties: false, + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + } + } + ] + }, + typeKeys: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`typeKeys`](typeKeys). To type in the element, make the element active with the `click` parameter.", + oneOf: [ + { + type: "string" + }, + { + type: "object", + additionalProperties: false, + properties: { + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + } + } + ] + }, + setVariables: { + type: "array", + description: "Extract environment variables from the element's text.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the element's text.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + required: [ + "action", + "selector" + ], + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + examples: [ + { + action: "find", + selector: "[title=Search]" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: "shorthair cat" + }, + { + action: "find", + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ], + delay: 100 + } + }, + { + action: "find", + selector: "[title=ResultsCount]", + setVariables: [ + { + name: "resultsCount", + regex: ".*" + } + ] + } + ] + }, + { + title: "wait", + type: "object", + description: "Pause before performing the next action.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "wait", + description: "The action to perform." + }, + duration: { + type: "number", + description: "Milliseconds to wait.", + default: 5e3 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "wait" + }, + { + action: "wait", + duration: 5e3 + } + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "steps" + ], + additionalProperties: false, + examples: [ + { + steps: [ + { + action: "checkLink", + url: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + action: "goTo", + url: "https://www.duckduckgo.com" + }, + { + action: "find", + selector: "[title=Search]", + click: true, + typeKeys: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + ] + }, + { + id: "Do all the things! - Test", + description: "This test includes every property across all actions.", + contexts: [ + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ], + setup: "setup.json", + cleanup: "cleanup.json", + steps: [ + { + action: "setVariables", + path: ".env" + }, + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "checkLink", + url: "https://www.duckduckgo.com" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "goTo", + url: "https://www.duckduckgo.com" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ] + } + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "saveScreenshot" + } + ] + }, + { + openApi: [ + { + name: "Acme", + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com" + } + ], + steps: [ + { + action: "httpRequest", + openApi: { + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + } + ] + } + ] + } + ] + } + } + }, + required: [ + "tests" + ], + examples: [ + { + tests: [ + { + steps: [ + { + action: "checkLink", + url: "https://www.duckduckgo.com" + } + ] + } + ] + }, + { + id: "Do all the things! - Spec", + contexts: [ + { + app: { + name: "chrome", + path: "/usr/bin/firefox" + }, + platforms: [ + "windows", + "mac" + ] + } + ], + tests: [ + { + id: "Do all the things! - Test", + description: "This test includes nearly every property across all actions.", + contexts: [ + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ], + steps: [ + { + action: "setVariables", + path: ".env" + }, + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "checkLink", + url: "https://www.duckduckgo.com" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "goTo", + url: "https://www.duckduckgo.com" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ] + } + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "saveScreenshot" + } + ] + } + ] + }, + { + id: "Make a request from an OpenAPI definition", + openApi: [ + { + name: "Acme", + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com" + } + ], + tests: [ + { + steps: [ + { + action: "httpRequest", + openApi: { + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + } + ] + } + ] + } + ] + }, + test_v2: { + title: "test", + type: "object", + description: "A Doc Detective test.", + properties: { + id: { + type: "string", + description: "Unique identifier for the test." + }, + description: { + type: "string", + description: "Description of the test." + }, + file: { + type: "string", + description: "Path to the file that the test is associated with." + }, + detectSteps: { + type: "boolean", + description: "Whether or not to detect steps in input files based on markup regex. Defaults to `true`." + }, + contexts: { + type: "array", + description: "Application/platform sets to run the test in. Overrides `contexts` defined at the config-level and spec-level.", + items: { + oneOf: [ + { + title: "context", + type: "object", + description: "An application and supported platforms.\n\nIf no contexts are specified but a context is required by one or more tests, Doc Detective attempts to identify a supported context in the current environment and run tests against it. For browsers, context priority is Firefox > Chrome > Chromium.", + properties: { + app: { + type: "object", + description: "The application to run.", + additionalProperties: false, + required: [ + "name" + ], + properties: { + name: { + type: "string", + description: "Name of the application.", + enum: [ + "chrome", + "firefox", + "safari", + "edge" + ] + }, + path: { + type: "string", + description: "Absolute path or command for the application. If not specified, defaults to typical install paths per platform. If specified but the path is invalid, the context is skipped." + }, + options: { + type: "object", + description: "Options to pass to the app. Only works when `name` is `firefox` or `chrome`.", + additionalProperties: false, + properties: { + width: { + type: "integer", + description: "Width of the window in pixels." + }, + height: { + type: "integer", + description: "Height of the window in pixels." + }, + viewport_height: { + type: "integer", + description: "Height of the viewport in pixels. Overrides `height`." + }, + viewport_width: { + type: "integer", + description: "Width of the viewport in pixels. Overrides `width`." + }, + headless: { + type: "boolean", + description: "If `true`, runs the browser in headless mode. Not supported by Safari." + }, + driverPath: { + type: "string", + description: "Path to the browser driver. If not specified, defaults to internally managed dependencies." + } + } + } + } + }, + platforms: { + description: "Supported platforms for the application.", + type: "array", + items: { + type: "string", + enum: [ + "linux", + "mac", + "windows" + ] + } + } + }, + required: [ + "app", + "platforms" + ], + additionalProperties: false, + examples: [ + { + app: { + name: "chrome" + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "chrome", + options: { + viewport_width: 800, + viewport_height: 600 + } + }, + platforms: [ + "linux" + ] + }, + { + app: { + name: "firefox", + options: { + width: 800, + height: 600, + headless: false, + driverPath: "/usr/bin/geckodriver" + } + }, + platforms: [ + "linux", + "windows", + "mac" + ] + }, + { + app: { + name: "safari" + }, + platforms: [ + "mac" + ] + }, + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ] + } + ] + } + }, + openApi: { + type: "array", + items: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + properties: { + operationId: { + type: "null", + $commment: "Only allow operationId at the step level." + } + }, + required: [ + "name", + "descriptionPath" + ] + } + ] + } + }, + setup: { + type: "string", + description: "Path to a test specification to perform before this test, while maintaining this test's context. Useful for setting up testing environments. Only the `steps` property is used from the first test in the setup spec." + }, + cleanup: { + type: "string", + description: "Path to a test specification to perform after this test, while maintaining this test's context. Useful for cleaning up testing environments. Only the `steps` property is used from the first test in the cleanup spec." + }, + steps: { + description: "Actions to perform as part of the test. Performed in the sequence defined. If one or more actions fail, the test fails.", + type: "array", + minItems: 1, + items: { + anyOf: [ + { + title: "checkLink", + type: "object", + description: "Check if a URL returns an acceptable status code from a GET request.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "checkLink", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to check.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200, + 201, + 202 + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "checkLink", + url: "https://www.google.com" + }, + { + action: "checkLink", + url: "https://www.google.com", + statusCodes: [ + 200 + ] + }, + { + action: "checkLink", + url: "/search", + origin: "www.google.com", + statusCodes: [ + 200 + ] + } + ] + }, + { + title: "goTo", + type: "object", + description: "Navigate to a specified URL.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "goTo", + description: "Action to perform." + }, + url: { + type: "string", + description: "URL to navigate to.", + pattern: "(^(http://|https://|/).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + origin: { + type: "string", + description: "Protocol and domain to navigate to. Prepended to `url`.", + transform: [ + "trim" + ] + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "url" + ], + additionalProperties: false, + examples: [ + { + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!", + action: "goTo", + url: "https://www.google.com" + }, + { + id: "ddec5e20-2e81-4f38-867c-92c8d9516756", + description: "This is a test!", + action: "goTo", + url: "/search", + origin: "https://www.google.com" + } + ] + }, + { + title: "httpRequest", + type: "object", + description: "Perform a generic HTTP request, for example to an API.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "httpRequest", + description: "Aciton to perform." + }, + url: { + type: "string", + description: "URL for the HTTP request.", + pattern: "(^(http://|https://).*|\\$[A-Za-z0-9_]+)", + transform: [ + "trim" + ] + }, + openApi: { + allOf: [ + { + version: "1.0.0", + $schema: "http://json-schema.org/draft-07/schema#", + title: "openApi", + type: "object", + description: "OpenAPI definition and configuration.", + additionalProperties: false, + properties: { + name: { + type: "string", + description: "Name of the OpenAPI definition, as defined in your configuration." + }, + descriptionPath: { + type: "string", + description: "URL or local path to the OpenAPI definition." + }, + operationId: { + type: "string", + description: "ID of the operation to use for the request." + }, + server: { + type: "string", + description: "Server to use for example requests. Only valid if `useExample` is `request` or `both`. If not specified but an example is used for the request, uses the first server defined in the OpenAPI definition." + }, + validateAgainstSchema: { + type: "string", + description: "Validates the request and/or response against the schema in the OpenAPI definition. If the request or response doesn't match the schema, the step fails.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "both" + }, + mockResponse: { + type: "boolean", + description: "If `true`, doesn't make the HTTP request, but instead uses the response example or schema from the OpenAPI definition as the response data. Useful for creating tests when an API isn't fully implemented yet. If `statusCode` isn't specified, uses the first defined response code." + }, + statusCode: { + type: "integer", + description: "Response code to use for validation, examples, and status code checking. If the response code doesn't match, the step fails. `statusCodes` overrides this value when specified." + }, + useExample: { + type: [ + "string" + ], + description: "Uses the example from the OpenAPI definition as the request and response data. If the request or response has multiple examples, specify `exampleKey`. If `statusCode` isn't specified, uses the first defined response code. `requestData`, `requestParams`, and `requestHeaders` override portions of request examples when specified. `responseData` overrides portions of response examples when specified.", + enum: [ + "request", + "response", + "both", + "none" + ], + default: "none" + }, + exampleKey: { + type: "string", + description: "Key of the example to use from the `examples` property in the OpenAPI definition. If an `examples` key isn't specified or isn't available for a given parameter or object, the `example` property value is used.", + default: "" + }, + requestHeaders: { + type: "object", + description: "Request headers to add to the request. If specified in both a config and a step, the step value overrides the config value.", + additionalProperties: { + type: "string" + } + } + }, + examples: [ + { + descriptionPath: "https://petstore.swagger.io/v2/swagger.json" + } + ] + }, + { + type: "object", + required: [ + "operationId" + ] + } + ] + }, + statusCodes: { + description: "Accepted status codes. If the specified URL returns a code other than what is specified here, the action fails.", + type: "array", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 200 + ] + }, + method: { + type: "string", + description: "Method of the HTTP request", + enum: [ + "get", + "put", + "post", + "patch", + "delete" + ], + transform: [ + "trim", + "toEnumCase" + ], + default: "get" + }, + timeout: { + type: "integer", + description: "Timeout for the HTTP request, in milliseconds.", + default: 6e4 + }, + requestHeaders: { + description: "Headers to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + responseHeaders: { + description: "Headers expected in the response, in key/value format. If one or more `responseHeaders` entries aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + properties: {}, + default: {} + }, + requestParams: { + description: "URL parameters to include in the HTTP request, in key/value format.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseParams: { + description: "DEPRECATED.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + requestData: { + description: "JSON object to include as the body of the HTTP request.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + responseData: { + description: "JSON object expected in the response. If one or more key/value pairs aren't present in the response, the step fails.", + type: "object", + additionalProperties: true, + default: {}, + properties: {} + }, + allowAdditionalFields: { + type: "boolean", + description: "If `false`, the step fails when the response data contains fields not specified in `responseData`.", + default: true + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`. Specify a file extension that matches the expected response type, such as `.json` for JSON content or `.txt` for strings." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + envsFromResponseData: { + description: "Environment variables to set based on response variables, as an object of the environment variable name and the jq filter applied to the response data to identify the variable's value.", + type: "array", + default: [], + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + jqFilter: { + description: "jq filter to apply to the response data. If the filter doesn't return a value, the environment variable isn't set.", + type: "string" + } + }, + required: [ + "name", + "jqFilter" + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + anyOf: [ + { + required: [ + "url" + ] + }, + { + required: [ + "openApi" + ] + } + ], + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "httpRequest", + url: "https://reqres.in/api/users" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users/2", + method: "put", + requestData: { + name: "morpheus", + job: "zion resident" + } + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "httpRequest", + url: "https://www.api-server.com", + method: "post", + timeout: 3e4, + requestHeaders: { + header: "value" + }, + requestParams: { + param: "value" + }, + requestData: { + field: "value" + }, + responseHeaders: { + header: "value" + }, + responseData: { + field: "value" + }, + statusCodes: [ + 200 + ] + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ], + savePath: "response.json", + saveDirectory: "media", + maxVariation: 5, + overwrite: "byVariation" + }, + { + action: "httpRequest", + openApi: { + name: "Reqres", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "createUser", + useExample: "both" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme" + } + }, + { + action: "httpRequest", + openApi: { + descriptionPath: "https://api.example.com/openapi.json", + operationId: "updateUser", + useExample: "request", + exampleKey: "acme", + requestHeaders: { + Authorization: "Bearer $TOKEN" + } + } + } + ] + }, + { + title: "runCode", + type: "object", + description: "Assemble and run code.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runCode", + description: "The action to perform." + }, + language: { + type: "string", + description: "Language of the code to run. If not specified, the code is run in the shell.", + enum: [ + "python", + "bash", + "javascript" + ] + }, + code: { + type: "string", + description: "Code to run." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + }, + outputs: { + type: "object", + description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.", + patternProperties: { + "^[A-Za-z0-9_]+$": { + type: "string", + description: "Runtime expression for a user-defined output value." + } + }, + properties: { + stdout: { + type: "string", + description: "Standard output of the command.", + readOnly: true + }, + stderr: { + type: "string", + description: "Standard error of the command.", + readOnly: true + }, + exitCode: { + type: "integer", + description: "Exit code of the command.", + readOnly: true + } + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "code", + "language" + ], + examples: [ + { + action: "runCode", + language: "javascript", + code: "console.log('Hello, ${process.env.USER}!');" + }, + { + action: "runCode", + language: "bash", + code: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runCode", + language: "javascript", + code: "return false", + exitCodes: [ + 1 + ] + }, + { + action: "runCode", + language: "python", + code: "print('Hello from Python')", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Python!", + savePath: "python-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + { + title: "runShell", + type: "object", + description: "Perform a native shell command.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "runShell", + description: "The action to perform." + }, + command: { + type: "string", + description: "Command to perform in the machine's default shell." + }, + args: { + type: "array", + description: "Arguments for the command.", + items: { + oneOf: [ + { + type: "string" + } + ] + }, + default: [] + }, + workingDirectory: { + type: "string", + description: "Working directory for the command.", + default: "." + }, + exitCodes: { + type: "array", + description: "Expected exit codes of the command. If the command's actual exit code isn't in this list, the step fails.", + items: { + oneOf: [ + { + type: "integer" + } + ] + }, + default: [ + 0 + ] + }, + output: { + type: "string", + description: "Content expected in the command's output. If the expected content can't be found in the command's output (either stdout or stderr), the step fails. Supports strings and regular expressions. To use a regular expression, the string must start and end with a forward slash, like in `/^hello-world.*/`." + }, + savePath: { + type: "string", + description: "File path to save the command's output, relative to `saveDirectory`." + }, + saveDirectory: { + type: "string", + description: "Directory to save the command's output. If the directory doesn't exist, creates the directory. If not specified, the directory is your media directory." + }, + maxVariation: { + type: "integer", + description: "Allowed variation in percentage of text different between the current output and previously saved output. If the difference between the current output and the previous output is greater than `maxVariation`, the step fails. If output doesn't exist at `savePath`, this value is ignored.", + default: 0, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing output at `savePath` if it exists.\nIf `byVariation`, overwrites the existing output at `savePath` if the difference between the new output and the existing output is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + timeout: { + type: "integer", + description: "Max time in milliseconds the command is allowed to run. If the command runs longer than this, the step fails.", + default: 6e4 + }, + setVariables: { + type: "array", + description: "Extract environment variables from the command's output.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the command's output.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + required: [ + "action", + "command" + ], + examples: [ + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "hello-world" + ], + id: "ddec5e20-2e81-4f38-867c-92c8d9516755", + description: "This is a test!" + }, + { + action: "runShell", + command: "docker run hello-world", + timeout: 2e4, + exitCodes: [ + 0 + ], + output: "Hello from Docker!" + }, + { + action: "runShell", + command: "false", + exitCodes: [ + 1 + ] + }, + { + action: "runShell", + command: "echo", + args: [ + "setup" + ], + exitCodes: [ + 0 + ], + output: "/.*?/", + setVariables: [ + { + name: "TEST", + regex: ".*" + } + ] + }, + { + action: "runShell", + command: "docker run hello-world", + workingDirectory: ".", + exitCodes: [ + 0 + ], + output: "Hello from Docker!", + savePath: "docker-output.txt", + saveDirectory: "output", + maxVariation: 10, + overwrite: "byVariation" + } + ] + }, + { + title: "saveScreenshot", + type: "object", + description: "Takes a screenshot in PNG format.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "saveScreenshot", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the PNG file, relative to `directory`. If not specified, the file name is the ID of the step.", + pattern: "([A-Za-z0-9_-]*\\.(png|PNG)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the PNG file. If the directory doesn't exist, creates the directory." + }, + maxVariation: { + type: "number", + description: "Allowed variation in percentage of pixels between the new screenshot and the exisitng screenshot at `path`. If the difference between the new screenshot and the existing screenshot is greater than `maxVariation`, the step fails. If a screenshot doesn't exist at `path`, this value is ignored.", + default: 5, + minimum: 0, + maximum: 100 + }, + overwrite: { + type: "string", + description: "If `true`, overwrites the existing screenshot at `path` if it exists.\nIf `byVariation`, overwrites the existing screenshot at `path` if the difference between the new screenshot and the existing screenshot is greater than `maxVariation`.", + enum: [ + "true", + "false", + "byVariation" + ], + default: "false" + }, + crop: { + type: "object", + description: "Crops the screenshot.", + properties: { + selector: { + type: "string", + description: "Selector of the element to crop the image to." + }, + padding: { + oneOf: [ + { + type: "number", + description: "Padding in pixels to add to the bounds of the element.", + minimum: 0 + }, + { + type: "object", + properties: { + top: { + type: "number", + minimum: 0 + }, + right: { + type: "number", + minimum: 0 + }, + bottom: { + type: "number", + minimum: 0 + }, + left: { + type: "number", + minimum: 0 + } + } + } + ] + } + }, + required: [ + "selector" + ], + additionalProperties: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "saveScreenshot" + }, + { + action: "saveScreenshot", + path: "results.png" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + maxVariation: 10, + overwrite: "byVariation" + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element" + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: 10 + } + }, + { + action: "saveScreenshot", + path: "results.png", + directory: "static/images", + crop: { + selector: "#element", + padding: { + top: 10, + right: 20, + bottom: 30, + left: 40 + } + } + } + ] + }, + { + title: "setVariables", + type: "object", + description: "Load environment variables from a `.env` file.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "setVariables", + description: "Action to perform." + }, + path: { + type: "string", + description: "Path to the `.env` file." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "path" + ], + additionalProperties: false, + examples: [ + { + action: "setVariables", + path: ".env" + } + ] + }, + { + title: "startRecording", + type: "object", + description: "Start recording the current browser viewport. Must be followed by a `stopRecording` action. Only runs when the context `app` is `chrome` and `headless` is `false`. Supported extensions: [ '.mp4', '.webm', '.gif' ]", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "startRecording", + description: "The action to perform." + }, + path: { + type: "string", + description: "File path of the recording. Supports the `.mp4`, `.webm`, and `.gif` extensions. If not specified, the file name is the ID of the step, and the extension is `.mp4`.", + pattern: "([A-Za-z0-9_-]*\\.(mp4|webm|gif)$|\\$[A-Za-z0-9_]+)" + }, + directory: { + type: "string", + description: "Directory of the file. Attempts to create the directory if it doesn't exist." + }, + overwrite: { + type: "boolean", + description: "If `true`, overwrites the existing file at `path` if it exists.", + default: false + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "startRecording" + }, + { + action: "startRecording", + path: "results.mp4" + }, + { + action: "startRecording", + path: "results.mp4", + directory: "static/media", + overwrite: true + } + ] + }, + { + title: "stopRecording", + type: "object", + description: "Stop the current recording.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "stopRecording", + description: "The action to perform." + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "stopRecording" + } + ] + }, + { + title: "typeKeys", + type: "object", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's enum. For example, to type the Escape key, enter `$ESCAPE$`.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "typeKeys", + description: "The action to perform." + }, + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "keys" + ], + additionalProperties: false, + examples: [ + { + action: "typeKeys", + keys: "kittens" + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "typeKeys", + keys: [ + "kittens", + "$ENTER$" + ], + delay: 500 + } + ] + }, + { + title: "find", + type: "object", + description: "Check if an element exists with the specified CSS selector.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "find", + description: "Action to perform." + }, + selector: { + description: "Selector that uniquely identifies the element.", + type: "string" + }, + timeout: { + type: "integer", + description: "Max duration in milliseconds to wait for the element to exist.", + default: 5e3 + }, + matchText: { + type: "string", + description: "Text that the element should contain. If the element doesn't contain the text, the step fails. Accepts both strings an regular expressions. To use a regular expression, the expression should start and end with a `/`. For example, `/search/`." + }, + moveTo: { + description: "Move to the element. If the element isn't visible, it's scrolled into view. Only runs the if the test is being recorded.", + oneOf: [ + { + type: "boolean" + } + ], + default: false + }, + click: { + description: "Click the element.", + oneOf: [ + { + type: "boolean", + default: false + }, + { + type: "object", + additionalProperties: false, + properties: { + button: { + description: "Kind of click to perform.", + type: "string", + enum: [ + "left", + "right", + "middle" + ] + } + } + } + ] + }, + typeKeys: { + description: "Type keys after finding the element. Either a string or an object with a `keys` field as defined in [`typeKeys`](typeKeys). To type in the element, make the element active with the `click` parameter.", + oneOf: [ + { + type: "string" + }, + { + type: "object", + additionalProperties: false, + properties: { + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + } + } + ] + }, + setVariables: { + type: "array", + description: "Extract environment variables from the element's text.", + items: { + oneOf: [ + { + description: "", + type: "object", + properties: { + name: { + description: "Name of the environment variable to set.", + type: "string" + }, + regex: { + description: "Regex to extract the environment variable from the element's text.", + type: "string" + } + }, + required: [ + "name", + "regex" + ] + } + ] + }, + default: [] + } + }, + required: [ + "action", + "selector" + ], + dynamicDefaults: { + id: "uuid" + }, + additionalProperties: false, + examples: [ + { + action: "find", + selector: "[title=Search]" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: "shorthair cat" + }, + { + action: "find", + selector: "[title=Search]", + click: { + button: "right" + } + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ], + delay: 100 + } + }, + { + action: "find", + selector: "[title=ResultsCount]", + setVariables: [ + { + name: "resultsCount", + regex: ".*" + } + ] + } + ] + }, + { + title: "wait", + type: "object", + description: "Pause before performing the next action.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "wait", + description: "The action to perform." + }, + duration: { + type: "number", + description: "Milliseconds to wait.", + default: 5e3 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "wait" + }, + { + action: "wait", + duration: 5e3 + } + ] + } + ] + } + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "steps" + ], + additionalProperties: false, + examples: [ + { + steps: [ + { + action: "checkLink", + url: "https://www.duckduckgo.com" + } + ] + }, + { + steps: [ + { + action: "goTo", + url: "https://www.duckduckgo.com" + }, + { + action: "find", + selector: "[title=Search]", + click: true, + typeKeys: { + keys: [ + "shorthair cats", + "$ENTER$" + ] + } + } + ] + }, + { + id: "Do all the things! - Test", + description: "This test includes every property across all actions.", + contexts: [ + { + app: { + name: "firefox", + path: "/usr/bin/firefox" + }, + platforms: [ + "linux" + ] + } + ], + setup: "setup.json", + cleanup: "cleanup.json", + steps: [ + { + action: "setVariables", + path: ".env" + }, + { + action: "runShell", + command: "echo", + args: [ + "$USER" + ] + }, + { + action: "checkLink", + url: "https://www.duckduckgo.com" + }, + { + action: "httpRequest", + url: "https://reqres.in/api/users", + method: "post", + requestData: { + name: "morpheus", + job: "leader" + }, + responseData: { + name: "morpheus", + job: "leader" + }, + statusCodes: [ + 200, + 201 + ] + }, + { + action: "goTo", + url: "https://www.duckduckgo.com" + }, + { + action: "find", + selector: "[title=Search]", + timeout: 1e4, + matchText: "Search", + moveTo: true, + click: true, + typeKeys: { + keys: [ + "shorthair cat" + ] + } + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "saveScreenshot" + } + ] + }, + { + openApi: [ + { + name: "Acme", + descriptionPath: "https://www.acme.com/openapi.json", + server: "https://api.acme.com" + } + ], + steps: [ + { + action: "httpRequest", + openApi: { + operationId: "getUserById" + }, + requestParams: { + id: 123 + } + } + ] + } + ] + }, + typeKeys_v2: { + title: "typeKeys", + type: "object", + description: "Type keys. To type special keys, begin and end the string with `$` and use the special key's enum. For example, to type the Escape key, enter `$ESCAPE$`.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "typeKeys", + description: "The action to perform." + }, + keys: { + description: "String of keys to enter.", + anyOf: [ + { + type: "string" + }, + { + type: "array", + items: { + oneOf: [ + { + type: "string" + } + ] + } + } + ] + }, + delay: { + type: "number", + description: "Delay in milliseconds between each key press. Only valid during a recording.", + default: 100 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action", + "keys" + ], + additionalProperties: false, + examples: [ + { + action: "typeKeys", + keys: "kittens" + }, + { + action: "typeKeys", + keys: [ + "$ENTER$" + ] + }, + { + action: "typeKeys", + keys: [ + "kittens", + "$ENTER$" + ], + delay: 500 + } + ] + }, + wait_v2: { + title: "wait", + type: "object", + description: "Pause before performing the next action.", + properties: { + id: { + type: "string", + description: "ID of the step." + }, + description: { + type: "string", + description: "Description of the step." + }, + action: { + type: "string", + const: "wait", + description: "The action to perform." + }, + duration: { + type: "number", + description: "Milliseconds to wait.", + default: 5e3 + } + }, + dynamicDefaults: { + id: "uuid" + }, + required: [ + "action" + ], + additionalProperties: false, + examples: [ + { + action: "wait" + }, + { + action: "wait", + duration: 5e3 + } + ] + } +}; + +// dist/schemas/index.js +var schemas = schemas_default; + +// dist/validate.js +var import_ajv = __toESM(require("ajv"), 1); +var import_ajv_formats = __toESM(require("ajv-formats"), 1); +var import_ajv_keywords = __toESM(require("ajv-keywords"), 1); +var import_ajv_errors = __toESM(require("ajv-errors"), 1); +var import_dynamicDefaults = __toESM(require("ajv-keywords/dist/definitions/dynamicDefaults.js"), 1); +function getRandomUUID() { + if (typeof crypto !== "undefined" && crypto.randomUUID) { + return crypto.randomUUID(); + } + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === "x" ? r : r & 3 | 8; + return v.toString(16); + }); +} +var ajv = new import_ajv.default({ + strictSchema: false, + useDefaults: true, + allErrors: true, + allowUnionTypes: true, + coerceTypes: true +}); +import_dynamicDefaults.default.DEFAULTS.uuid = (_args) => getRandomUUID; +(0, import_ajv_formats.default)(ajv); +(0, import_ajv_keywords.default)(ajv); +(0, import_ajv_errors.default)(ajv); +for (const [key, value] of Object.entries(schemas)) { + ajv.addSchema(value, key); +} +var compatibleSchemas = { + config_v3: ["config_v2"], + context_v3: ["context_v2"], + openApi_v3: ["openApi_v2"], + spec_v3: ["spec_v2"], + step_v3: [ + "checkLink_v2", + "find_v2", + "goTo_v2", + "httpRequest_v2", + "runShell_v2", + "runCode_v2", + "saveScreenshot_v2", + "setVariables_v2", + "startRecording_v2", + "stopRecording_v2", + "typeKeys_v2", + "wait_v2" + ], + test_v3: ["test_v2"] +}; +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function validate({ schemaKey, object, addDefaults = true }) { + if (!schemaKey) { + throw new Error("Schema key is required."); + } + if (!object) { + throw new Error("Object is required."); + } + const result = { + valid: false, + errors: "", + object + }; + let validationObject; + let check = ajv.getSchema(schemaKey); + if (!check) { + result.valid = false; + result.errors = `Schema not found: ${schemaKey}`; + result.object = object; + return result; + } + validationObject = JSON.parse(JSON.stringify(object)); + result.valid = check(validationObject); + result.errors = ""; + if (check.errors) { + const compatibleSchemasList = compatibleSchemas[schemaKey]; + if (!compatibleSchemasList) { + result.errors = check.errors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", "); + result.object = object; + result.valid = false; + return result; + } + const matchedSchemaKey = compatibleSchemasList.find((key) => { + validationObject = JSON.parse(JSON.stringify(object)); + const check2 = ajv.getSchema(key); + if (check2 && check2(validationObject)) + return key; + }); + if (!matchedSchemaKey) { + result.errors = check.errors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", "); + result.object = object; + result.valid = false; + return result; + } else { + const transformedObject = transformToSchemaKey({ + currentSchema: matchedSchemaKey, + targetSchema: schemaKey, + object: validationObject + }); + result.valid = check(transformedObject); + if (result.valid) { + validationObject = transformedObject; + object = transformedObject; + } else if (check.errors) { + const errors = check.errors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`); + result.errors = errors.join(", "); + return result; + } + } + } + if (addDefaults) { + result.object = validationObject; + } else { + result.object = object; + } + return result; +} +function transformToSchemaKey({ currentSchema = "", targetSchema = "", object = {} }) { + if (currentSchema === targetSchema) { + return object; + } + const compatibleList = compatibleSchemas[targetSchema]; + if (!compatibleList || !compatibleList.includes(currentSchema)) { + throw new Error(`Can't transform from ${currentSchema} to ${targetSchema}.`); + } + if (targetSchema === "step_v3") { + const transformedObject = { + stepId: object.id, + description: object.description + }; + if (currentSchema === "goTo_v2") { + transformedObject.goTo = { + url: object.url, + origin: object.origin + }; + } else if (currentSchema === "checkLink_v2") { + transformedObject.checkLink = { + url: object.url, + origin: object.origin, + statusCodes: object.statusCodes + }; + } else if (currentSchema === "find_v2") { + transformedObject.find = { + selector: object.selector, + elementText: object.matchText, + timeout: object.timeout, + moveTo: object.moveTo, + click: object.click, + type: object.typeKeys + }; + if (typeof object.typeKeys === "object" && object.typeKeys.keys) { + transformedObject.find.type.inputDelay = object.typeKeys.delay; + delete transformedObject.find.type.delay; + } + transformedObject.variables = {}; + object.setVariables?.forEach((variable) => { + transformedObject.variables[variable.name] = `extract($$element.text, "${variable.regex}")`; + }); + } else if (currentSchema === "httpRequest_v2") { + transformedObject.httpRequest = { + method: object.method, + url: object.url, + openApi: object.openApi, + request: { + body: object.requestData, + headers: object.requestHeaders, + parameters: object.requestParams + }, + response: { + body: object.responseData, + headers: object.responseHeaders + }, + statusCodes: object.statusCodes, + allowAdditionalFields: object.allowAdditionalFields, + timeout: object.timeout, + path: object.savePath, + directory: object.saveDirectory, + maxVariation: object.maxVariation / 100, + overwrite: object.overwrite === "byVariation" ? "aboveVariation" : object.overwrite + }; + if (object.openApi) { + transformedObject.httpRequest.openApi = transformToSchemaKey({ + currentSchema: "openApi_v2", + targetSchema: "openApi_v3", + object: object.openApi + }); + } + transformedObject.variables = {}; + object.envsFromResponseData?.forEach((variable) => { + transformedObject.variables[variable.name] = `jq($$response.body, "${variable.jqFilter}")`; + }); + } else if (currentSchema === "runShell_v2") { + transformedObject.runShell = { + command: object.command, + args: object.args, + workingDirectory: object.workingDirectory, + exitCodes: object.exitCodes, + stdio: object.output, + path: object.savePath, + directory: object.saveDirectory, + maxVariation: object.maxVariation / 100, + overwrite: object.overwrite === "byVariation" ? "aboveVariation" : object.overwrite, + timeout: object.timeout + }; + transformedObject.variables = {}; + object.setVariables?.forEach((variable) => { + transformedObject.variables[variable.name] = `extract($$stdio.stdout, "${variable.regex}")`; + }); + } else if (currentSchema === "runCode_v2") { + transformedObject.runCode = { + language: object.language, + code: object.code, + args: object.args, + workingDirectory: object.workingDirectory, + exitCodes: object.exitCodes, + stdio: object.output, + path: object.savePath, + directory: object.saveDirectory, + maxVariation: object.maxVariation / 100, + overwrite: object.overwrite === "byVariation" ? "aboveVariation" : object.overwrite, + timeout: object.timeout + }; + transformedObject.variables = {}; + object?.setVariables?.forEach((variable) => { + transformedObject.variables[variable.name] = `extract($$stdio.stdout, "${variable.regex}")`; + }); + } else if (currentSchema === "setVariables_v2") { + transformedObject.loadVariables = object.path; + } else if (currentSchema === "typeKeys_v2") { + transformedObject.type = { + keys: object.keys, + inputDelay: object.delay + }; + } else if (currentSchema === "saveScreenshot_v2") { + transformedObject.screenshot = { + path: object.path, + directory: object.directory, + maxVariation: object.maxVariation / 100, + overwrite: object.overwrite === "byVariation" ? "aboveVariation" : object.overwrite, + crop: object.crop + }; + } else if (currentSchema === "startRecording_v2") { + transformedObject.record = { + path: object.path, + directory: object.directory, + overwrite: object.overwrite + }; + } else if (currentSchema === "stopRecording_v2") { + transformedObject.stopRecord = true; + } else if (currentSchema === "wait_v2") { + transformedObject.wait = object; + } + const result = validate({ + schemaKey: "step_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } else if (targetSchema === "config_v3") { + const transformedObject = { + loadVariables: object.envVariables, + input: object?.runTests?.input || object.input, + output: object?.runTests?.output || object.output, + recursive: object?.runTests?.recursive || object.recursive, + relativePathBase: object.relativePathBase, + detectSteps: object?.runTests?.detectSteps, + beforeAny: object?.runTests?.setup, + afterAll: object?.runTests?.cleanup, + logLevel: object.logLevel, + telemetry: object.telemetry + }; + if (object?.runTests?.contexts) + transformedObject.runOn = object.runTests.contexts.map((context) => transformToSchemaKey({ + currentSchema: "context_v2", + targetSchema: "context_v3", + object: context + })); + if (object?.integrations?.openApi) { + transformedObject.integrations = {}; + transformedObject.integrations.openApi = object.integrations.openApi.map((description) => transformToSchemaKey({ + currentSchema: "openApi_v2", + targetSchema: "openApi_v3", + object: description + })); + } + if (object?.fileTypes) + transformedObject.fileTypes = object.fileTypes.map((fileType) => { + const transformedFileType = { + name: fileType.name, + extensions: fileType.extensions.map((extension) => ( + // Trim leading `.` from extension + extension.replace(/^\./, "") + )), + inlineStatements: { + // Convert strings to regex, escaping special characters + testStart: `${escapeRegExp(fileType.testStartStatementOpen)}(.*?)${escapeRegExp(fileType.testStartStatementClose)}`, + testEnd: escapeRegExp(fileType.testEndStatement), + ignoreStart: escapeRegExp(fileType.testIgnoreStatement), + step: `${escapeRegExp(fileType.stepStatementOpen)}(.*?)${escapeRegExp(fileType.stepStatementClose)}` + } + }; + if (fileType.markup) + transformedFileType.markup = fileType.markup.map((markup) => { + const transformedMarkup = { + name: markup.name, + regex: markup.regex + }; + if (markup.actions) + transformedMarkup.actions = markup.actions.map((action) => { + if (typeof action === "string") + return action; + if (typeof action === "object") { + if (action.params) { + action = { + action: action.name, + ...action.params + }; + } + const transformedAction = transformToSchemaKey({ + currentSchema: `${action.action}_v2`, + targetSchema: "step_v3", + object: action + }); + return transformedAction; + } + }); + return transformedMarkup; + }); + return transformedFileType; + }); + const result = validate({ + schemaKey: "config_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } else if (targetSchema === "context_v3") { + const transformedObject = {}; + transformedObject.platforms = object.platforms; + if (object.app?.name) { + const name = object.app.name === "edge" ? "chrome" : object.app?.name; + transformedObject.browsers = []; + transformedObject.browsers.push({ + name, + headless: object.app?.options?.headless, + window: { + width: object.app?.options?.width, + height: object.app?.options?.height + }, + viewport: { + width: object.app?.options?.viewport_width, + height: object.app?.options?.viewport_height + } + }); + } + const result = validate({ + schemaKey: "context_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } else if (targetSchema === "openApi_v3") { + let transformedObject; + const { name, requestHeaders, ...intermediaryObject } = object; + intermediaryObject.name = object.name; + intermediaryObject.headers = object.requestHeaders; + transformedObject = { ...intermediaryObject }; + const result = validate({ + schemaKey: "openApi_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } else if (targetSchema === "spec_v3") { + const transformedObject = { + specId: object.id, + description: object.description, + contentPath: object.file + }; + if (object.contexts) + transformedObject.runOn = object.contexts.map((context) => transformToSchemaKey({ + currentSchema: "context_v2", + targetSchema: "context_v3", + object: context + })); + if (object.openApi) + transformedObject.openApi = object.openApi.map((description) => transformToSchemaKey({ + currentSchema: "openApi_v2", + targetSchema: "openApi_v3", + object: description + })); + transformedObject.tests = object.tests.map((test) => transformToSchemaKey({ + currentSchema: "test_v2", + targetSchema: "test_v3", + object: test + })); + const result = validate({ + schemaKey: "spec_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } else if (targetSchema === "test_v3") { + const transformedObject = { + testId: object.id, + description: object.description, + contentPath: object.file, + detectSteps: object.detectSteps, + before: object.setup, + after: object.cleanup + }; + if (object.contexts) + transformedObject.runOn = object.contexts.map((context) => transformToSchemaKey({ + currentSchema: "context_v2", + targetSchema: "context_v3", + object: context + })); + if (object.openApi) + transformedObject.openApi = object.openApi.map((description) => transformToSchemaKey({ + currentSchema: "openApi_v2", + targetSchema: "openApi_v3", + object: description + })); + transformedObject.steps = object.steps.map((step) => transformToSchemaKey({ + currentSchema: `${step.action}_v2`, + targetSchema: "step_v3", + object: step + })); + const result = validate({ + schemaKey: "test_v3", + object: transformedObject + }); + if (!result.valid) { + throw new Error(`Invalid object: ${result.errors}`); + } + return result.object; + } + return null; +} + +// dist/detectTests.js +var import_yaml = __toESM(require("yaml"), 1); +function generateUUID() { + if (typeof crypto !== "undefined" && crypto.randomUUID) { + return crypto.randomUUID(); + } + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === "x" ? r : r & 3 | 8; + return v.toString(16); + }); +} +async function detectTests(input) { + return parseContent({ + config: input.config || {}, + content: input.content, + filePath: input.filePath, + fileType: input.fileType + }); +} +function parseXmlAttributes({ stringifiedObject }) { + if (typeof stringifiedObject !== "string") { + return null; + } + const str = stringifiedObject.trim(); + if (str.startsWith("{") || str.startsWith("[")) { + return null; + } + const yamlPattern = /^\w+:\s/; + if (yamlPattern.test(str)) { + return null; + } + if (str.startsWith("-")) { + return null; + } + const result = {}; + const attrRegex = /([\w.]+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g; + let match; + let hasMatches = false; + while ((match = attrRegex.exec(str)) !== null) { + hasMatches = true; + const keyPath = match[1]; + let value = match[2] !== void 0 ? match[2] : match[3] !== void 0 ? match[3] : match[4]; + if (value === "true") { + value = true; + } else if (value === "false") { + value = false; + } else if (!isNaN(value) && value !== "") { + value = Number(value); + } + if (keyPath.includes(".")) { + const keys = keyPath.split("."); + let current = result; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key] || typeof current[key] !== "object") { + current[key] = {}; + } + current = current[key]; + } + current[keys[keys.length - 1]] = value; + } else { + result[keyPath] = value; + } + } + return hasMatches ? result : null; +} +function parseObject({ stringifiedObject }) { + if (typeof stringifiedObject === "string") { + const xmlAttrs = parseXmlAttributes({ stringifiedObject }); + if (xmlAttrs !== null) { + return xmlAttrs; + } + try { + const json = JSON.parse(stringifiedObject); + return json; + } catch (jsonError) { + const trimmedString = stringifiedObject.trim(); + const looksLikeEscapedJson = (trimmedString.startsWith("{") || trimmedString.startsWith("[")) && trimmedString.includes('\\"'); + if (looksLikeEscapedJson) { + try { + const stringToParse = JSON.parse('"' + stringifiedObject + '"'); + return JSON.parse(stringToParse); + } catch { + try { + const unescaped = stringifiedObject.replace(/\\"/g, '"'); + return JSON.parse(unescaped); + } catch { + } + } + } + try { + const yaml = import_yaml.default.parse(stringifiedObject); + return yaml; + } catch (yamlError) { + return null; + } + } + } + return stringifiedObject; +} +function replaceNumericVariables(stringOrObjectSource, values) { + let stringOrObject = JSON.parse(JSON.stringify(stringOrObjectSource)); + if (typeof stringOrObject !== "string" && typeof stringOrObject !== "object") { + throw new Error("Invalid stringOrObject type"); + } + if (typeof values !== "object") { + throw new Error("Invalid values type"); + } + if (typeof stringOrObject === "string") { + const matches = stringOrObject.match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + return null; + } else { + stringOrObject = stringOrObject.replace(/\$[0-9]+/g, (variable) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + if (typeof stringOrObject === "object") { + Object.keys(stringOrObject).forEach((key) => { + if (typeof stringOrObject[key] === "object") { + stringOrObject[key] = replaceNumericVariables(stringOrObject[key], values); + } else if (typeof stringOrObject[key] === "string") { + const matches = stringOrObject[key].match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + delete stringOrObject[key]; + } else { + stringOrObject[key] = stringOrObject[key].replace(/\$[0-9]+/g, (variable) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + }); + } + return stringOrObject; +} +async function parseContent({ config, content, filePath, fileType }) { + const statements = []; + const statementTypes = ["testStart", "testEnd", "ignoreStart", "ignoreEnd", "step"]; + function findTest({ tests: tests2, testId: testId2 }) { + let test = tests2.find((t) => t.testId === testId2); + if (!test) { + test = { testId: testId2, steps: [] }; + tests2.push(test); + } + return test; + } + statementTypes.forEach((statementType) => { + if (typeof fileType.inlineStatements === "undefined" || typeof fileType.inlineStatements[statementType] === "undefined") + return; + fileType.inlineStatements[statementType].forEach((statementRegex) => { + const regex = new RegExp(statementRegex, "g"); + const matches = [...content.matchAll(regex)]; + matches.forEach((match) => { + match.type = statementType; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + }); + }); + if (config.detectSteps && fileType.markup) { + fileType.markup.forEach((markup) => { + markup.regex.forEach((pattern) => { + const regex = new RegExp(pattern, "g"); + const matches = [...content.matchAll(regex)]; + if (matches.length > 0 && markup.batchMatches) { + const combinedMatch = { + 1: matches.map((match) => match[1] || match[0]).join("\n"), + type: "detectedStep", + markup, + sortIndex: Math.min(...matches.map((match) => match.index)) + }; + statements.push(combinedMatch); + } else if (matches.length > 0) { + matches.forEach((match) => { + match.type = "detectedStep"; + match.markup = markup; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + } + }); + }); + } + statements.sort((a, b) => a.sortIndex - b.sortIndex); + let tests = []; + let testId = generateUUID(); + let ignore = false; + statements.forEach((statement) => { + let test; + let statementContent = ""; + let stepsCleanup = false; + switch (statement.type) { + case "testStart": { + statementContent = statement[1] || statement[0]; + const parsedTest = parseObject({ stringifiedObject: statementContent }); + if (!parsedTest || typeof parsedTest !== "object") + break; + test = parsedTest; + if (test.id || test.file || test.setup || test.cleanup) { + if (!test.steps) { + test.steps = [{ action: "goTo", url: "https://doc-detective.com" }]; + stepsCleanup = true; + } + const transformed = transformToSchemaKey({ + object: test, + currentSchema: "test_v2", + targetSchema: "test_v3" + }); + test = transformed; + if (stepsCleanup && test) { + test.steps = []; + } + } + if (test.testId) { + testId = test.testId; + } else { + test.testId = testId; + } + if (test.detectSteps === "false") { + test.detectSteps = false; + } else if (test.detectSteps === "true") { + test.detectSteps = true; + } + if (!test.steps) { + test.steps = []; + } + tests.push(test); + break; + } + case "testEnd": + testId = generateUUID(); + ignore = false; + break; + case "ignoreStart": + ignore = true; + break; + case "ignoreEnd": + ignore = false; + break; + case "detectedStep": + if (ignore) + break; + test = findTest({ tests, testId }); + if (typeof test.detectSteps !== "undefined" && !test.detectSteps) { + break; + } + if (statement?.markup?.actions) { + statement.markup.actions.forEach((action) => { + let step = {}; + if (typeof action === "string") { + if (action === "runCode") + return; + step[action] = statement[1] || statement[0]; + if (config.origin && (action === "goTo" || action === "checkLink")) { + step[action] = { ...step[action], origin: config.origin }; + } + if (action === "screenshot" && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + const screenshotPath = step[action]; + step[action] = { + path: screenshotPath, + sourceIntegration: { + type: "heretto", + integrationName: herettoIntegration, + filePath: screenshotPath, + contentPath: filePath + } + }; + } + } + } else { + const replacedStep = replaceNumericVariables(action, statement); + if (!replacedStep || typeof replacedStep === "string") + return; + step = replacedStep; + if (step.screenshot && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + if (typeof step.screenshot === "string") { + step.screenshot = { path: step.screenshot }; + } else if (typeof step.screenshot === "boolean") { + step.screenshot = {}; + } + step.screenshot.sourceIntegration = { + type: "heretto", + integrationName: herettoIntegration, + filePath: step.screenshot.path || "", + contentPath: filePath + }; + } + } + } + if (step.httpRequest?.request) { + if (typeof step.httpRequest.request.headers === "string") { + try { + const headers = {}; + step.httpRequest.request.headers.split("\n").forEach((header) => { + const colonIndex = header.indexOf(":"); + if (colonIndex === -1) + return; + const key = header.substring(0, colonIndex).trim(); + const value = header.substring(colonIndex + 1).trim(); + if (key && value) { + headers[key] = value; + } + }); + step.httpRequest.request.headers = headers; + } catch (error) { + } + } + if (typeof step.httpRequest.request.body === "string" && (step.httpRequest.request.body.trim().startsWith("{") || step.httpRequest.request.body.trim().startsWith("["))) { + try { + step.httpRequest.request.body = JSON.parse(step.httpRequest.request.body); + } catch (error) { + } + } + } + const valid = validate({ + schemaKey: "step_v3", + object: step, + addDefaults: false + }); + if (!valid.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = valid.object; + test.steps.push(step); + }); + } + break; + case "step": { + if (ignore) + break; + test = findTest({ tests, testId }); + statementContent = statement[1] || statement[0]; + const parsedStep = parseObject({ stringifiedObject: statementContent }); + if (!parsedStep || typeof parsedStep !== "object") + break; + let step = parsedStep; + const validation = validate({ + schemaKey: "step_v3", + object: step, + addDefaults: false + }); + if (!validation.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = validation.object; + test.steps.push(step); + break; + } + /* c8 ignore next 2 - all statement types are handled above */ + default: + break; + } + }); + const validatedTests = []; + tests.forEach((test) => { + const validation = validate({ + schemaKey: "test_v3", + object: test, + addDefaults: false + }); + if (!validation.valid) { + log(config, "warn", `Couldn't convert test in ${filePath} to valid test. Skipping.`); + return; + } + validatedTests.push(validation.object); + }); + return validatedTests; +} +function findHerettoIntegration(config, filePath) { + if (!config._herettoPathMapping) + return null; + const normalizedFilePath = filePath.replace(/\\/g, "/"); + for (const [outputPath, integrationName] of Object.entries(config._herettoPathMapping)) { + const normalizedOutputPath = outputPath.replace(/\\/g, "/"); + if (normalizedFilePath.startsWith(normalizedOutputPath)) { + return integrationName; + } + } + return null; +} +function log(config, level, message) { + const logLevels = ["silent", "error", "warn", "info", "debug"]; + const configLevel = (config.logLevel || "info") === "warning" ? "warn" : config.logLevel || "info"; + const normalizedLevel = level === "warning" ? "warn" : level; + const configLevelIndex = logLevels.indexOf(configLevel); + const messageLevelIndex = logLevels.indexOf(normalizedLevel); + if (configLevelIndex < 0 || messageLevelIndex < 0) + return; + if (messageLevelIndex > configLevelIndex) + return; + if (normalizedLevel === "silent") + return; + if (typeof message === "object") { + console[normalizedLevel](JSON.stringify(message, null, 2)); + } else { + console[normalizedLevel](message); + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + detectTests, + schemas, + transformToSchemaKey, + validate +}); diff --git a/dist/index.d.cts b/dist/index.d.cts new file mode 100644 index 00000000..acc9a98e --- /dev/null +++ b/dist/index.d.cts @@ -0,0 +1,4 @@ +export { schemas, SchemaKey, Schema } from "./schemas/index.js"; +export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate.js"; +export { detectTests, DetectTestsInput, DetectedTest, DetectTestsConfig, FileType } from "./detectTests.js"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index c39e0fb3..acc9a98e 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,5 +1,4 @@ -export { schemas, SchemaKey, Schema } from "./schemas"; -export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate"; -export { resolvePaths, ResolvePathsOptions } from "./resolvePaths"; -export { readFile, ReadFileOptions } from "./files"; +export { schemas, SchemaKey, Schema } from "./schemas/index.js"; +export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate.js"; +export { detectTests, DetectTestsInput, DetectedTest, DetectTestsConfig, FileType } from "./detectTests.js"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index e6055ce4..c944c32f 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 7eabb7d6..8e9f7bc4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,13 +1,4 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.readFile = exports.resolvePaths = exports.transformToSchemaKey = exports.validate = exports.schemas = void 0; -var schemas_1 = require("./schemas"); -Object.defineProperty(exports, "schemas", { enumerable: true, get: function () { return schemas_1.schemas; } }); -var validate_1 = require("./validate"); -Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return validate_1.validate; } }); -Object.defineProperty(exports, "transformToSchemaKey", { enumerable: true, get: function () { return validate_1.transformToSchemaKey; } }); -var resolvePaths_1 = require("./resolvePaths"); -Object.defineProperty(exports, "resolvePaths", { enumerable: true, get: function () { return resolvePaths_1.resolvePaths; } }); -var files_1 = require("./files"); -Object.defineProperty(exports, "readFile", { enumerable: true, get: function () { return files_1.readFile; } }); +export { schemas } from "./schemas/index.js"; +export { validate, transformToSchemaKey } from "./validate.js"; +export { detectTests } from "./detectTests.js"; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map index 2a48b11e..ea5f96e2 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAuD;AAA9C,kGAAA,OAAO,OAAA;AAChB,uCAA+G;AAAtG,oGAAA,QAAQ,OAAA;AAAE,gHAAA,oBAAoB,OAAA;AACvC,+CAAmE;AAA1D,4GAAA,YAAY,OAAA;AACrB,iCAAoD;AAA3C,iGAAA,QAAQ,OAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAqB,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAqD,MAAM,eAAe,CAAC;AAClH,OAAO,EAAE,WAAW,EAA+D,MAAM,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs index d3b65c2b..2b517797 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -1,4 +1,4 @@ // ESM wrapper for CommonJS output import cjsModule from './index.js'; -export const { schemas, validate, transformToSchemaKey, resolvePaths, readFile } = cjsModule; +export const { schemas, validate, transformToSchemaKey, detectTests, parseContent, parseXmlAttributes, parseObject, replaceNumericVariables, log, resolvePaths, readFile } = cjsModule; export default cjsModule; diff --git a/dist/schemas/index.d.ts.map b/dist/schemas/index.d.ts.map index 5beddea0..35f43e0a 100644 --- a/dist/schemas/index.d.ts.map +++ b/dist/schemas/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAC;AAEzC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AACjD,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC;AAErD,eAAO,MAAM,OAAO,EAAE,OAAO,WAAyB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAuB;AAE/D,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AACjD,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC;AAErD,eAAO,MAAM,OAAO,EAAE,OAAO,WAAyB,CAAC"} \ No newline at end of file diff --git a/dist/schemas/index.js b/dist/schemas/index.js index c1f4fc05..21e95c91 100644 --- a/dist/schemas/index.js +++ b/dist/schemas/index.js @@ -1,9 +1,3 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.schemas = void 0; -const schemas_json_1 = __importDefault(require("./schemas.json")); -exports.schemas = schemas_json_1.default; +import schemasJson from "./schemas.json" with { type: "json" }; +export const schemas = schemasJson; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/schemas/index.js.map b/dist/schemas/index.js.map index 4ad52a28..806a18d9 100644 --- a/dist/schemas/index.js.map +++ b/dist/schemas/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":";;;;;;AAAA,kEAAyC;AAK5B,QAAA,OAAO,GAAuB,sBAAW,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAK/D,MAAM,CAAC,MAAM,OAAO,GAAuB,WAAW,CAAC"} \ No newline at end of file diff --git a/dist/types/generated/checkLink_v3.js b/dist/types/generated/checkLink_v3.js index af7927d6..5fbcb942 100644 --- a/dist/types/generated/checkLink_v3.js +++ b/dist/types/generated/checkLink_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from checkLink_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=checkLink_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/checkLink_v3.js.map b/dist/types/generated/checkLink_v3.js.map index 3af782d0..0a2b024e 100644 --- a/dist/types/generated/checkLink_v3.js.map +++ b/dist/types/generated/checkLink_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"checkLink_v3.js","sourceRoot":"","sources":["../../../src/types/generated/checkLink_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"checkLink_v3.js","sourceRoot":"","sources":["../../../src/types/generated/checkLink_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/click_v3.js b/dist/types/generated/click_v3.js index e91b65b4..46770200 100644 --- a/dist/types/generated/click_v3.js +++ b/dist/types/generated/click_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from click_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=click_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/click_v3.js.map b/dist/types/generated/click_v3.js.map index d878a37f..331fa8f6 100644 --- a/dist/types/generated/click_v3.js.map +++ b/dist/types/generated/click_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"click_v3.js","sourceRoot":"","sources":["../../../src/types/generated/click_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"click_v3.js","sourceRoot":"","sources":["../../../src/types/generated/click_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/config_v3.js b/dist/types/generated/config_v3.js index 8cd0ed27..8708eb89 100644 --- a/dist/types/generated/config_v3.js +++ b/dist/types/generated/config_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from config_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=config_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/config_v3.js.map b/dist/types/generated/config_v3.js.map index 4e8308cf..6e3f587f 100644 --- a/dist/types/generated/config_v3.js.map +++ b/dist/types/generated/config_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"config_v3.js","sourceRoot":"","sources":["../../../src/types/generated/config_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"config_v3.js","sourceRoot":"","sources":["../../../src/types/generated/config_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/context_v3.js b/dist/types/generated/context_v3.js index 39feaeda..8508e1bf 100644 --- a/dist/types/generated/context_v3.js +++ b/dist/types/generated/context_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from context_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=context_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/context_v3.js.map b/dist/types/generated/context_v3.js.map index a593df9a..05aec2ce 100644 --- a/dist/types/generated/context_v3.js.map +++ b/dist/types/generated/context_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"context_v3.js","sourceRoot":"","sources":["../../../src/types/generated/context_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"context_v3.js","sourceRoot":"","sources":["../../../src/types/generated/context_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/dragAndDrop_v3.js b/dist/types/generated/dragAndDrop_v3.js index 5fb2a52f..fc1f1ecd 100644 --- a/dist/types/generated/dragAndDrop_v3.js +++ b/dist/types/generated/dragAndDrop_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from dragAndDrop_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=dragAndDrop_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/dragAndDrop_v3.js.map b/dist/types/generated/dragAndDrop_v3.js.map index 8cef45b6..01cfbeb2 100644 --- a/dist/types/generated/dragAndDrop_v3.js.map +++ b/dist/types/generated/dragAndDrop_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"dragAndDrop_v3.js","sourceRoot":"","sources":["../../../src/types/generated/dragAndDrop_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"dragAndDrop_v3.js","sourceRoot":"","sources":["../../../src/types/generated/dragAndDrop_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/endRecord_v3.js b/dist/types/generated/endRecord_v3.js index 3798788a..fa8a55db 100644 --- a/dist/types/generated/endRecord_v3.js +++ b/dist/types/generated/endRecord_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from endRecord_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=endRecord_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/endRecord_v3.js.map b/dist/types/generated/endRecord_v3.js.map index 6861b0d1..45b7eb86 100644 --- a/dist/types/generated/endRecord_v3.js.map +++ b/dist/types/generated/endRecord_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"endRecord_v3.js","sourceRoot":"","sources":["../../../src/types/generated/endRecord_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"endRecord_v3.js","sourceRoot":"","sources":["../../../src/types/generated/endRecord_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/find_v3.js b/dist/types/generated/find_v3.js index 82e9dc14..0bc3a431 100644 --- a/dist/types/generated/find_v3.js +++ b/dist/types/generated/find_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from find_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=find_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/find_v3.js.map b/dist/types/generated/find_v3.js.map index 857ed32d..ef3d0c9b 100644 --- a/dist/types/generated/find_v3.js.map +++ b/dist/types/generated/find_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"find_v3.js","sourceRoot":"","sources":["../../../src/types/generated/find_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"find_v3.js","sourceRoot":"","sources":["../../../src/types/generated/find_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/goTo_v3.js b/dist/types/generated/goTo_v3.js index 45a3393d..364e69cd 100644 --- a/dist/types/generated/goTo_v3.js +++ b/dist/types/generated/goTo_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from goTo_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=goTo_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/goTo_v3.js.map b/dist/types/generated/goTo_v3.js.map index 80f590a6..788b3903 100644 --- a/dist/types/generated/goTo_v3.js.map +++ b/dist/types/generated/goTo_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"goTo_v3.js","sourceRoot":"","sources":["../../../src/types/generated/goTo_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"goTo_v3.js","sourceRoot":"","sources":["../../../src/types/generated/goTo_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/httpRequest_v3.js b/dist/types/generated/httpRequest_v3.js index eaf0d7c5..0b77afdb 100644 --- a/dist/types/generated/httpRequest_v3.js +++ b/dist/types/generated/httpRequest_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from httpRequest_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=httpRequest_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/httpRequest_v3.js.map b/dist/types/generated/httpRequest_v3.js.map index 37d7cf93..df658e30 100644 --- a/dist/types/generated/httpRequest_v3.js.map +++ b/dist/types/generated/httpRequest_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"httpRequest_v3.js","sourceRoot":"","sources":["../../../src/types/generated/httpRequest_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"httpRequest_v3.js","sourceRoot":"","sources":["../../../src/types/generated/httpRequest_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/loadCookie_v3.js b/dist/types/generated/loadCookie_v3.js index 39480269..70225757 100644 --- a/dist/types/generated/loadCookie_v3.js +++ b/dist/types/generated/loadCookie_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from loadCookie_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=loadCookie_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/loadCookie_v3.js.map b/dist/types/generated/loadCookie_v3.js.map index 8b0c0780..2ef08105 100644 --- a/dist/types/generated/loadCookie_v3.js.map +++ b/dist/types/generated/loadCookie_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"loadCookie_v3.js","sourceRoot":"","sources":["../../../src/types/generated/loadCookie_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"loadCookie_v3.js","sourceRoot":"","sources":["../../../src/types/generated/loadCookie_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/loadVariables_v3.js b/dist/types/generated/loadVariables_v3.js index cc6f98a9..134eac79 100644 --- a/dist/types/generated/loadVariables_v3.js +++ b/dist/types/generated/loadVariables_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from loadVariables_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=loadVariables_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/loadVariables_v3.js.map b/dist/types/generated/loadVariables_v3.js.map index 79ffdf89..28b8c838 100644 --- a/dist/types/generated/loadVariables_v3.js.map +++ b/dist/types/generated/loadVariables_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"loadVariables_v3.js","sourceRoot":"","sources":["../../../src/types/generated/loadVariables_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"loadVariables_v3.js","sourceRoot":"","sources":["../../../src/types/generated/loadVariables_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/openApi_v3.js b/dist/types/generated/openApi_v3.js index 7a692a77..7b748d31 100644 --- a/dist/types/generated/openApi_v3.js +++ b/dist/types/generated/openApi_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from openApi_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=openApi_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/openApi_v3.js.map b/dist/types/generated/openApi_v3.js.map index b2c48afb..20c76fbc 100644 --- a/dist/types/generated/openApi_v3.js.map +++ b/dist/types/generated/openApi_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"openApi_v3.js","sourceRoot":"","sources":["../../../src/types/generated/openApi_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"openApi_v3.js","sourceRoot":"","sources":["../../../src/types/generated/openApi_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/record_v3.js b/dist/types/generated/record_v3.js index 6ae0b9d4..ccb70978 100644 --- a/dist/types/generated/record_v3.js +++ b/dist/types/generated/record_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from record_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=record_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/record_v3.js.map b/dist/types/generated/record_v3.js.map index d20811a9..7fa2e67a 100644 --- a/dist/types/generated/record_v3.js.map +++ b/dist/types/generated/record_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"record_v3.js","sourceRoot":"","sources":["../../../src/types/generated/record_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"record_v3.js","sourceRoot":"","sources":["../../../src/types/generated/record_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/report_v3.js b/dist/types/generated/report_v3.js index 30fcf555..97ed992a 100644 --- a/dist/types/generated/report_v3.js +++ b/dist/types/generated/report_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from report_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=report_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/report_v3.js.map b/dist/types/generated/report_v3.js.map index 99f4de79..a86fca53 100644 --- a/dist/types/generated/report_v3.js.map +++ b/dist/types/generated/report_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"report_v3.js","sourceRoot":"","sources":["../../../src/types/generated/report_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"report_v3.js","sourceRoot":"","sources":["../../../src/types/generated/report_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/resolvedTests_v3.js b/dist/types/generated/resolvedTests_v3.js index 47ff3de0..d4729547 100644 --- a/dist/types/generated/resolvedTests_v3.js +++ b/dist/types/generated/resolvedTests_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from resolvedTests_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=resolvedTests_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/resolvedTests_v3.js.map b/dist/types/generated/resolvedTests_v3.js.map index bc6670eb..ddd11ec6 100644 --- a/dist/types/generated/resolvedTests_v3.js.map +++ b/dist/types/generated/resolvedTests_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"resolvedTests_v3.js","sourceRoot":"","sources":["../../../src/types/generated/resolvedTests_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"resolvedTests_v3.js","sourceRoot":"","sources":["../../../src/types/generated/resolvedTests_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/runCode_v3.js b/dist/types/generated/runCode_v3.js index c86160c8..6eb2452e 100644 --- a/dist/types/generated/runCode_v3.js +++ b/dist/types/generated/runCode_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from runCode_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=runCode_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/runCode_v3.js.map b/dist/types/generated/runCode_v3.js.map index bdae44dd..34913b65 100644 --- a/dist/types/generated/runCode_v3.js.map +++ b/dist/types/generated/runCode_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"runCode_v3.js","sourceRoot":"","sources":["../../../src/types/generated/runCode_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"runCode_v3.js","sourceRoot":"","sources":["../../../src/types/generated/runCode_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/runShell_v3.js b/dist/types/generated/runShell_v3.js index 0344000b..08620343 100644 --- a/dist/types/generated/runShell_v3.js +++ b/dist/types/generated/runShell_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from runShell_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=runShell_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/runShell_v3.js.map b/dist/types/generated/runShell_v3.js.map index 4699103a..4362a751 100644 --- a/dist/types/generated/runShell_v3.js.map +++ b/dist/types/generated/runShell_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"runShell_v3.js","sourceRoot":"","sources":["../../../src/types/generated/runShell_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"runShell_v3.js","sourceRoot":"","sources":["../../../src/types/generated/runShell_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/saveCookie_v3.js b/dist/types/generated/saveCookie_v3.js index 3d20ebd9..306687ce 100644 --- a/dist/types/generated/saveCookie_v3.js +++ b/dist/types/generated/saveCookie_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from saveCookie_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=saveCookie_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/saveCookie_v3.js.map b/dist/types/generated/saveCookie_v3.js.map index 2847053f..55dc7134 100644 --- a/dist/types/generated/saveCookie_v3.js.map +++ b/dist/types/generated/saveCookie_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"saveCookie_v3.js","sourceRoot":"","sources":["../../../src/types/generated/saveCookie_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"saveCookie_v3.js","sourceRoot":"","sources":["../../../src/types/generated/saveCookie_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/screenshot_v3.js b/dist/types/generated/screenshot_v3.js index 55a24991..55dc86e1 100644 --- a/dist/types/generated/screenshot_v3.js +++ b/dist/types/generated/screenshot_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from screenshot_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=screenshot_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/screenshot_v3.js.map b/dist/types/generated/screenshot_v3.js.map index dc948dfa..8f92ff6f 100644 --- a/dist/types/generated/screenshot_v3.js.map +++ b/dist/types/generated/screenshot_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"screenshot_v3.js","sourceRoot":"","sources":["../../../src/types/generated/screenshot_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"screenshot_v3.js","sourceRoot":"","sources":["../../../src/types/generated/screenshot_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/sourceIntegration_v3.js b/dist/types/generated/sourceIntegration_v3.js index 3948c292..d743d81f 100644 --- a/dist/types/generated/sourceIntegration_v3.js +++ b/dist/types/generated/sourceIntegration_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from sourceIntegration_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=sourceIntegration_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/sourceIntegration_v3.js.map b/dist/types/generated/sourceIntegration_v3.js.map index 05caba88..96d2386f 100644 --- a/dist/types/generated/sourceIntegration_v3.js.map +++ b/dist/types/generated/sourceIntegration_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"sourceIntegration_v3.js","sourceRoot":"","sources":["../../../src/types/generated/sourceIntegration_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"sourceIntegration_v3.js","sourceRoot":"","sources":["../../../src/types/generated/sourceIntegration_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/spec_v3.js b/dist/types/generated/spec_v3.js index 768b6153..ddeaf547 100644 --- a/dist/types/generated/spec_v3.js +++ b/dist/types/generated/spec_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from spec_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=spec_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/spec_v3.js.map b/dist/types/generated/spec_v3.js.map index f67faf5d..2762d8dd 100644 --- a/dist/types/generated/spec_v3.js.map +++ b/dist/types/generated/spec_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"spec_v3.js","sourceRoot":"","sources":["../../../src/types/generated/spec_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"spec_v3.js","sourceRoot":"","sources":["../../../src/types/generated/spec_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/step_v3.js b/dist/types/generated/step_v3.js index 5c252f76..1ee2358f 100644 --- a/dist/types/generated/step_v3.js +++ b/dist/types/generated/step_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from step_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=step_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/step_v3.js.map b/dist/types/generated/step_v3.js.map index 2218dcfa..5ffa9777 100644 --- a/dist/types/generated/step_v3.js.map +++ b/dist/types/generated/step_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"step_v3.js","sourceRoot":"","sources":["../../../src/types/generated/step_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"step_v3.js","sourceRoot":"","sources":["../../../src/types/generated/step_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/stopRecord_v3.js b/dist/types/generated/stopRecord_v3.js index a92b8eb3..00fd5fb8 100644 --- a/dist/types/generated/stopRecord_v3.js +++ b/dist/types/generated/stopRecord_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from stopRecord_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=stopRecord_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/stopRecord_v3.js.map b/dist/types/generated/stopRecord_v3.js.map index 8ded45b9..35a25ac6 100644 --- a/dist/types/generated/stopRecord_v3.js.map +++ b/dist/types/generated/stopRecord_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"stopRecord_v3.js","sourceRoot":"","sources":["../../../src/types/generated/stopRecord_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"stopRecord_v3.js","sourceRoot":"","sources":["../../../src/types/generated/stopRecord_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/test_v3.js b/dist/types/generated/test_v3.js index 386b70db..f6fd39e6 100644 --- a/dist/types/generated/test_v3.js +++ b/dist/types/generated/test_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from test_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=test_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/test_v3.js.map b/dist/types/generated/test_v3.js.map index c5482cc2..bc621999 100644 --- a/dist/types/generated/test_v3.js.map +++ b/dist/types/generated/test_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"test_v3.js","sourceRoot":"","sources":["../../../src/types/generated/test_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"test_v3.js","sourceRoot":"","sources":["../../../src/types/generated/test_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/type_v3.js b/dist/types/generated/type_v3.js index 61a55f4f..87588174 100644 --- a/dist/types/generated/type_v3.js +++ b/dist/types/generated/type_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from type_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=type_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/type_v3.js.map b/dist/types/generated/type_v3.js.map index ae60ebb7..937ff632 100644 --- a/dist/types/generated/type_v3.js.map +++ b/dist/types/generated/type_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"type_v3.js","sourceRoot":"","sources":["../../../src/types/generated/type_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"type_v3.js","sourceRoot":"","sources":["../../../src/types/generated/type_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/types/generated/wait_v3.js b/dist/types/generated/wait_v3.js index 964ec3b2..6bb999a2 100644 --- a/dist/types/generated/wait_v3.js +++ b/dist/types/generated/wait_v3.js @@ -1,8 +1,7 @@ -"use strict"; /* eslint-disable */ /** * Auto-generated from wait_v3.schema.json * Do not edit manually */ -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; //# sourceMappingURL=wait_v3.js.map \ No newline at end of file diff --git a/dist/types/generated/wait_v3.js.map b/dist/types/generated/wait_v3.js.map index 4ad1662f..b2f3b5d4 100644 --- a/dist/types/generated/wait_v3.js.map +++ b/dist/types/generated/wait_v3.js.map @@ -1 +1 @@ -{"version":3,"file":"wait_v3.js","sourceRoot":"","sources":["../../../src/types/generated/wait_v3.ts"],"names":[],"mappings":";AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file +{"version":3,"file":"wait_v3.js","sourceRoot":"","sources":["../../../src/types/generated/wait_v3.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;GAGG"} \ No newline at end of file diff --git a/dist/validate.d.ts.map b/dist/validate.d.ts.map index 22771565..08c1ccc8 100644 --- a/dist/validate.d.ts.map +++ b/dist/validate.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AA2DA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC;CACb;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,WAAkB,GACnB,EAAE,eAAe,GAAG,cAAc,CA8FlC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAkB,EAClB,YAAiB,EACjB,MAAW,GACZ,EAAE,gBAAgB,GAAG,GAAG,CA4YxB"} \ No newline at end of file +{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AA2EA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;IACZ,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,GAAG,CAAC;CACb;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,WAAkB,GACnB,EAAE,eAAe,GAAG,cAAc,CA8FlC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAkB,EAClB,YAAiB,EACjB,MAAW,GACZ,EAAE,gBAAgB,GAAG,GAAG,CA4YxB"} \ No newline at end of file diff --git a/dist/validate.js b/dist/validate.js index 00cedc79..67415ba0 100644 --- a/dist/validate.js +++ b/dist/validate.js @@ -1,21 +1,27 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.validate = validate; -exports.transformToSchemaKey = transformToSchemaKey; -const schemas_1 = require("./schemas"); -const ajv_1 = __importDefault(require("ajv")); +import { schemas } from "./schemas/index.js"; +import Ajv from "ajv"; // Ajv extra formats: https://ajv.js.org/packages/ajv-formats.html -const ajv_formats_1 = __importDefault(require("ajv-formats")); +import addFormats from "ajv-formats"; // Ajv extra keywords: https://ajv.js.org/packages/ajv-keywords.html -const ajv_keywords_1 = __importDefault(require("ajv-keywords")); +import addKeywords from "ajv-keywords"; // Ajv custom errors: https://ajv.js.org/packages/ajv-errors.html -const ajv_errors_1 = __importDefault(require("ajv-errors")); -const crypto_1 = require("crypto"); +import addErrors from "ajv-errors"; +import dynamicDefaultsDef from "ajv-keywords/dist/definitions/dynamicDefaults.js"; +// Browser-compatible UUID function +/* c8 ignore next 10 - crypto.randomUUID always available in Node.js; fallback is for browsers */ +function getRandomUUID() { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} // Configure base Ajv -const ajv = new ajv_1.default({ +// @ts-expect-error - CJS/ESM interop: Ajv constructor is callable at runtime +const ajv = new Ajv({ strictSchema: false, useDefaults: true, allErrors: true, @@ -23,15 +29,17 @@ const ajv = new ajv_1.default({ coerceTypes: true, }); // Enable `uuid` dynamic default -// @ts-ignore - ajv-keywords has incomplete types for dynamicDefaults -const def = require("ajv-keywords/dist/definitions/dynamicDefaults"); -def.DEFAULTS.uuid = () => crypto_1.randomUUID; +// @ts-expect-error - CJS/ESM interop: dynamicDefaultsDef.DEFAULTS exists at runtime +dynamicDefaultsDef.DEFAULTS.uuid = (_args) => getRandomUUID; // Enhance Ajv -(0, ajv_formats_1.default)(ajv); -(0, ajv_keywords_1.default)(ajv); -(0, ajv_errors_1.default)(ajv); +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime +addFormats(ajv); +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime +addKeywords(ajv); +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime +addErrors(ajv); // Add all schemas from `schema` object. -for (const [key, value] of Object.entries(schemas_1.schemas)) { +for (const [key, value] of Object.entries(schemas)) { ajv.addSchema(value, key); } // Define the specific schemas that have compatibility mappings @@ -78,7 +86,7 @@ function escapeRegExp(string) { * * @throws {Error} If {@link schemaKey} or {@link object} is missing. */ -function validate({ schemaKey, object, addDefaults = true, }) { +export function validate({ schemaKey, object, addDefaults = true, }) { if (!schemaKey) { throw new Error("Schema key is required."); } @@ -166,7 +174,7 @@ function validate({ schemaKey, object, addDefaults = true, }) { * @returns The transformed object conforming to the target schema. * @throws {Error} If transformation between the specified schemas is not supported or if the transformed object fails validation. */ -function transformToSchemaKey({ currentSchema = "", targetSchema = "", object = {}, }) { +export function transformToSchemaKey({ currentSchema = "", targetSchema = "", object = {}, }) { // Check if the current schema is the same as the target schema if (currentSchema === targetSchema) { return object; diff --git a/dist/validate.js.map b/dist/validate.js.map index 97519809..d96eb8e2 100644 --- a/dist/validate.js.map +++ b/dist/validate.js.map @@ -1 +1 @@ -{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":";;;;;AAoGA,4BAkGC;AAYD,oDAgZC;AAlmBD,uCAA+C;AAC/C,8CAA4C;AAC5C,kEAAkE;AAClE,8DAAqC;AACrC,oEAAoE;AACpE,gEAAuC;AACvC,iEAAiE;AACjE,4DAAmC;AACnC,mCAAoC;AAEpC,qBAAqB;AACrB,MAAM,GAAG,GAAG,IAAI,aAAG,CAAC;IAClB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,IAAI;IACf,eAAe,EAAE,IAAI;IACrB,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC;AAEH,gCAAgC;AAChC,qEAAqE;AACrE,MAAM,GAAG,GAAG,OAAO,CAAC,+CAA+C,CAAC,CAAC;AACrE,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,mBAAU,CAAC;AAErC,cAAc;AACd,IAAA,qBAAU,EAAC,GAAG,CAAC,CAAC;AAChB,IAAA,sBAAW,EAAC,GAAG,CAAC,CAAC;AACjB,IAAA,oBAAS,EAAC,GAAG,CAAC,CAAC;AAEf,wCAAwC;AACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAO,CAAC,EAAE,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,+DAA+D;AAC/D,MAAM,iBAAiB,GAAG;IACxB,SAAS,EAAE,CAAC,WAAW,CAAC;IACxB,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,OAAO,EAAE;QACP,cAAc;QACd,SAAS;QACT,SAAS;QACT,gBAAgB;QAChB,aAAa;QACb,YAAY;QACZ,mBAAmB;QACnB,iBAAiB;QACjB,mBAAmB;QACnB,kBAAkB;QAClB,aAAa;QACb,SAAS;KACV;IACD,OAAO,EAAE,CAAC,SAAS,CAAC;CACZ,CAAC;AAsBX;;;;;GAKG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,oCAAoC;AAC5F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,WAAW,GAAG,IAAI,GACF;IAChB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAmB;QAC7B,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,MAAM;KACf,CAAC;IACF,IAAI,gBAAqB,CAAC;IAC1B,IAAI,KAAK,GAAiC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,qBAAqB,SAAS,EAAE,CAAC;QACjD,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,0DAA0D;IAC1D,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAEtD,oDAAoD;IACpD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACvC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;IAEnB,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,wDAAwD;QACxD,MAAM,qBAAqB,GACzB,iBAAiB,CAAC,SAA2C,CAAC,CAAC;QACjE,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC3B,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;iBACzB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP;iBACA,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC1D,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YACtD,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,KAAK,IAAI,KAAK,CAAC,gBAAgB,CAAC;gBAAE,OAAO,GAAG,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;iBACzB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP;iBACA,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,OAAO,MAAM,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;gBAC7C,aAAa,EAAE,gBAAgB;gBAC/B,YAAY,EAAE,SAAS;gBACvB,MAAM,EAAE,gBAAgB;aACzB,CAAC,CAAC;YAEH,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACxC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,gBAAgB,GAAG,iBAAiB,CAAC;gBACrC,MAAM,GAAG,iBAAiB,CAAC;gBAC3B,oGAAoG;YACtG,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAC7B,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP,CAAC;gBACF,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClC,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,oBAAoB;QACtB,CAAC;IACH,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,oBAAoB,CAAC,EACnC,aAAa,GAAG,EAAE,EAClB,YAAY,GAAG,EAAE,EACjB,MAAM,GAAG,EAAE,GACM;IACjB,+DAA+D;IAC/D,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,mEAAmE;IACnE,MAAM,cAAc,GAAG,iBAAiB,CAAC,YAAmC,CAAC,CAAC;IAC9E,IACE,CAAC,cAAc;QACf,CAAE,cAAoC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAC9D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,wBAAwB,aAAa,OAAO,YAAY,GAAG,CAC5D,CAAC;IACJ,CAAC;IACD,uBAAuB;IACvB,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC;QACF,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,iBAAiB,CAAC,IAAI,GAAG;gBACvB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,cAAc,EAAE,CAAC;YAC5C,iBAAiB,CAAC,SAAS,GAAG;gBAC5B,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACvC,iBAAiB,CAAC,IAAI,GAAG;gBACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,WAAW,EAAE,MAAM,CAAC,SAAS;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,IAAI,EAAE,MAAM,CAAC,QAAQ;aACtB,CAAC;YACF,mCAAmC;YACnC,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAChE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC/D,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAC3C,CAAC;YACD,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC7C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;YAC9C,iBAAiB,CAAC,WAAW,GAAG;gBAC9B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM,CAAC,WAAW;oBACxB,OAAO,EAAE,MAAM,CAAC,cAAc;oBAC9B,UAAU,EAAE,MAAM,CAAC,aAAa;iBACjC;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,MAAM,CAAC,YAAY;oBACzB,OAAO,EAAE,MAAM,CAAC,eAAe;iBAChC;gBACD,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;gBACnD,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;aACvB,CAAC;YACF,2CAA2C;YAC3C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,iBAAiB,CAAC,WAAW,CAAC,OAAO,GAAG,oBAAoB,CAAC;oBAC3D,aAAa,EAAE,YAAY;oBAC3B,YAAY,EAAE,YAAY;oBAC1B,MAAM,EAAE,MAAM,CAAC,OAAO;iBACvB,CAAC,CAAC;YACL,CAAC;YACD,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBACrD,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,wBAAwB,QAAQ,CAAC,QAAQ,IAAI,CAAC;YAClD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,aAAa,EAAE,CAAC;YAC3C,iBAAiB,CAAC,QAAQ,GAAG;gBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;YACF,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC7C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;YAC1C,iBAAiB,CAAC,OAAO,GAAG;gBAC1B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;YACF,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC9C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,iBAAiB,EAAE,CAAC;YAC/C,iBAAiB,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC;QAChD,CAAC;aAAM,IAAI,aAAa,KAAK,aAAa,EAAE,CAAC;YAC3C,iBAAiB,CAAC,IAAI,GAAG;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,UAAU,EAAE,MAAM,CAAC,KAAK;aACzB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACjD,iBAAiB,CAAC,UAAU,GAAG;gBAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACjD,iBAAiB,CAAC,MAAM,GAAG;gBACzB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,kBAAkB,EAAE,CAAC;YAChD,iBAAiB,CAAC,UAAU,GAAG,IAAI,CAAC;QACtC,CAAC;aAAM,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACvC,iBAAiB,CAAC,IAAI,GAAG,MAAM,CAAC;QAClC,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,+CAA+C;QAC/C,MAAM,iBAAiB,GAAQ;YAC7B,aAAa,EAAE,MAAM,CAAC,YAAY;YAClC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK;YAC9C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM;YACjD,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,IAAI,MAAM,CAAC,SAAS;YAC1D,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW;YAC1C,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK;YAClC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;YACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC;QACF,gCAAgC;QAChC,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ;YAC5B,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CACtE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,gCAAgC;QAChC,IAAI,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;YAClC,iBAAiB,CAAC,YAAY,GAAG,EAAE,CAAC;YACpC,iBAAiB,CAAC,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CACtE,CAAC,WAAgB,EAAE,EAAE,CACnB,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACL,CAAC;QACJ,CAAC;QACD,kCAAkC;QAClC,IAAI,MAAM,EAAE,SAAS;YACnB,iBAAiB,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAa,EAAE,EAAE;gBACnE,MAAM,mBAAmB,GAAQ;oBAC/B,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAiB,EAAE,EAAE;oBACxD,kCAAkC;oBAClC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAC7B;oBACD,gBAAgB,EAAE;wBAChB,wDAAwD;wBACxD,SAAS,EAAE,GAAG,YAAY,CACxB,QAAQ,CAAC,sBAAsB,CAChC,QAAQ,YAAY,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE;wBACzD,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC,gBAAgB,CAAC;wBAChD,WAAW,EAAE,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC;wBACvD,IAAI,EAAE,GAAG,YAAY,CACnB,QAAQ,CAAC,iBAAiB,CAC3B,QAAQ,YAAY,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE;qBACrD;iBACF,CAAC;gBACF,IAAI,QAAQ,CAAC,MAAM;oBACjB,mBAAmB,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE;wBAC/D,MAAM,iBAAiB,GAAQ;4BAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;4BACjB,KAAK,EAAE,MAAM,CAAC,KAAK;yBACpB,CAAC;wBACF,IAAI,MAAM,CAAC,OAAO;4BAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE;gCAC7D,IAAI,OAAO,MAAM,KAAK,QAAQ;oCAAE,OAAO,MAAM,CAAC;gCAC9C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;oCAC/B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;wCAClB,MAAM,GAAG;4CACP,MAAM,EAAE,MAAM,CAAC,IAAI;4CACnB,GAAG,MAAM,CAAC,MAAM;yCACjB,CAAC;oCACJ,CAAC;oCACD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;wCAC7C,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,KAAK;wCACpC,YAAY,EAAE,SAAS;wCACvB,MAAM,EAAE,MAAM;qCACf,CAAC,CAAC;oCACH,OAAO,iBAAiB,CAAC;gCAC3B,CAAC;4BACH,CAAC,CAAC,CAAC;wBAEL,OAAO,iBAAiB,CAAC;oBAC3B,CAAC,CAAC,CAAC;gBACL,OAAO,mBAAmB,CAAC;YAC7B,CAAC,CAAC,CAAC;QACL,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,yEAAyE;QACzE,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QACzC,MAAM,iBAAiB,GAAQ,EAAE,CAAC;QAClC,iDAAiD;QACjD,iBAAiB,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAC/C,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;YACtE,iBAAiB,CAAC,QAAQ,GAAG,EAAE,CAAC;YAChC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC9B,IAAI;gBACJ,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ;gBACvC,MAAM,EAAE;oBACN,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK;oBACjC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM;iBACpC;gBACD,QAAQ,EAAE;oBACR,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc;oBAC1C,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe;iBAC7C;aACF,CAAC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QACzC,IAAI,iBAAsB,CAAC;QAC3B,iDAAiD;QACjD,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE,GAAG,MAAM,CAAC;QAC/D,kBAAkB,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACtC,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;QACnD,iBAAiB,GAAG,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAE9C,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,2CAA2C;QAC3C,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,WAAW,EAAE,MAAM,CAAC,IAAI;SACzB,CAAC;QACF,IAAI,MAAM,CAAC,QAAQ;YACjB,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAC7D,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,IAAI,MAAM,CAAC,OAAO;YAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAgB,EAAE,EAAE,CAClE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACH,CAAC;QACJ,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CACvD,oBAAoB,CAAC;YACnB,aAAa,EAAE,SAAS;YACxB,YAAY,EAAE,SAAS;YACvB,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,6DAA6D;QAC7D,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,2CAA2C;QAC3C,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,WAAW,EAAE,MAAM,CAAC,IAAI;YACxB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,MAAM,EAAE,MAAM,CAAC,KAAK;YACpB,KAAK,EAAE,MAAM,CAAC,OAAO;SACtB,CAAC;QACF,IAAI,MAAM,CAAC,QAAQ;YACjB,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAC7D,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,IAAI,MAAM,CAAC,OAAO;YAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAgB,EAAE,EAAE,CAClE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACH,CAAC;QACJ,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CACvD,oBAAoB,CAAC;YACnB,aAAa,EAAE,GAAG,IAAI,CAAC,MAAM,KAAK;YAClC,YAAY,EAAE,SAAS;YACvB,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,6DAA6D;QAC7D,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IACD,8EAA8E;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC"} \ No newline at end of file +{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAa,MAAM,oBAAoB,CAAC;AACxD,OAAO,GAAyB,MAAM,KAAK,CAAC;AAC5C,kEAAkE;AAClE,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,oEAAoE;AACpE,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,iEAAiE;AACjE,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,kBAAkB,MAAM,kDAAkD,CAAC;AAElF,mCAAmC;AACnC,iGAAiG;AACjG,SAAS,aAAa;IACpB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,UAAS,CAAC;QACvE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qBAAqB;AACrB,6EAA6E;AAC7E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IAClB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,IAAI;IACf,eAAe,EAAE,IAAI;IACrB,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC;AAEH,gCAAgC;AAChC,oFAAoF;AACpF,kBAAkB,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,KAAU,EAAE,EAAE,CAAC,aAAa,CAAC;AAEjE,cAAc;AACd,mFAAmF;AACnF,UAAU,CAAC,GAAG,CAAC,CAAC;AAChB,mFAAmF;AACnF,WAAW,CAAC,GAAG,CAAC,CAAC;AACjB,mFAAmF;AACnF,SAAS,CAAC,GAAG,CAAC,CAAC;AAEf,wCAAwC;AACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,+DAA+D;AAC/D,MAAM,iBAAiB,GAAG;IACxB,SAAS,EAAE,CAAC,WAAW,CAAC;IACxB,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,OAAO,EAAE;QACP,cAAc;QACd,SAAS;QACT,SAAS;QACT,gBAAgB;QAChB,aAAa;QACb,YAAY;QACZ,mBAAmB;QACnB,iBAAiB;QACjB,mBAAmB;QACnB,kBAAkB;QAClB,aAAa;QACb,SAAS;KACV;IACD,OAAO,EAAE,CAAC,SAAS,CAAC;CACZ,CAAC;AAsBX;;;;;GAKG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,oCAAoC;AAC5F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ,CAAC,EACvB,SAAS,EACT,MAAM,EACN,WAAW,GAAG,IAAI,GACF;IAChB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAmB;QAC7B,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,MAAM;KACf,CAAC;IACF,IAAI,gBAAqB,CAAC;IAC1B,IAAI,KAAK,GAAiC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,qBAAqB,SAAS,EAAE,CAAC;QACjD,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,0DAA0D;IAC1D,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAEtD,oDAAoD;IACpD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACvC,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;IAEnB,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,wDAAwD;QACxD,MAAM,qBAAqB,GACzB,iBAAiB,CAAC,SAA2C,CAAC,CAAC;QACjE,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC3B,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;iBACzB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP;iBACA,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC1D,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YACtD,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,KAAK,IAAI,KAAK,CAAC,gBAAgB,CAAC;gBAAE,OAAO,GAAG,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;iBACzB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP;iBACA,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACrB,OAAO,MAAM,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;gBAC7C,aAAa,EAAE,gBAAgB;gBAC/B,YAAY,EAAE,SAAS;gBACvB,MAAM,EAAE,gBAAgB;aACzB,CAAC,CAAC;YAEH,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACxC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,gBAAgB,GAAG,iBAAiB,CAAC;gBACrC,MAAM,GAAG,iBAAiB,CAAC;gBAC3B,oGAAoG;YACtG,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAC7B,CAAC,KAAK,EAAE,EAAE,CACR,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,SAAS,CACvD,KAAK,CAAC,MAAM,CACb,GAAG,CACP,CAAC;gBACF,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClC,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,oBAAoB;QACtB,CAAC;IACH,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,MAAM,GAAG,gBAAgB,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,aAAa,GAAG,EAAE,EAClB,YAAY,GAAG,EAAE,EACjB,MAAM,GAAG,EAAE,GACM;IACjB,+DAA+D;IAC/D,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,mEAAmE;IACnE,MAAM,cAAc,GAAG,iBAAiB,CAAC,YAAmC,CAAC,CAAC;IAC9E,IACE,CAAC,cAAc;QACf,CAAE,cAAoC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAC9D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,wBAAwB,aAAa,OAAO,YAAY,GAAG,CAC5D,CAAC;IACJ,CAAC;IACD,uBAAuB;IACvB,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC;QACF,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,iBAAiB,CAAC,IAAI,GAAG;gBACvB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,cAAc,EAAE,CAAC;YAC5C,iBAAiB,CAAC,SAAS,GAAG;gBAC5B,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACvC,iBAAiB,CAAC,IAAI,GAAG;gBACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,WAAW,EAAE,MAAM,CAAC,SAAS;gBAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,IAAI,EAAE,MAAM,CAAC,QAAQ;aACtB,CAAC;YACF,mCAAmC;YACnC,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAChE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAC/D,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAC3C,CAAC;YACD,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC7C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;YAC9C,iBAAiB,CAAC,WAAW,GAAG;gBAC9B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM,CAAC,WAAW;oBACxB,OAAO,EAAE,MAAM,CAAC,cAAc;oBAC9B,UAAU,EAAE,MAAM,CAAC,aAAa;iBACjC;gBACD,QAAQ,EAAE;oBACR,IAAI,EAAE,MAAM,CAAC,YAAY;oBACzB,OAAO,EAAE,MAAM,CAAC,eAAe;iBAChC;gBACD,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;gBACnD,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;aACvB,CAAC;YACF,2CAA2C;YAC3C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,iBAAiB,CAAC,WAAW,CAAC,OAAO,GAAG,oBAAoB,CAAC;oBAC3D,aAAa,EAAE,YAAY;oBAC3B,YAAY,EAAE,YAAY;oBAC1B,MAAM,EAAE,MAAM,CAAC,OAAO;iBACvB,CAAC,CAAC;YACL,CAAC;YACD,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBACrD,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,wBAAwB,QAAQ,CAAC,QAAQ,IAAI,CAAC;YAClD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,aAAa,EAAE,CAAC;YAC3C,iBAAiB,CAAC,QAAQ,GAAG;gBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;YACF,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC7C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;YAC1C,iBAAiB,CAAC,OAAO,GAAG;gBAC1B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,KAAK,EAAE,MAAM,CAAC,MAAM;gBACpB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,SAAS,EAAE,MAAM,CAAC,aAAa;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;YACF,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;YACjC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,QAAa,EAAE,EAAE;gBAC9C,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACxC,4BAA4B,QAAQ,CAAC,KAAK,IAAI,CAAC;YACnD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,aAAa,KAAK,iBAAiB,EAAE,CAAC;YAC/C,iBAAiB,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC;QAChD,CAAC;aAAM,IAAI,aAAa,KAAK,aAAa,EAAE,CAAC;YAC3C,iBAAiB,CAAC,IAAI,GAAG;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,UAAU,EAAE,MAAM,CAAC,KAAK;aACzB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACjD,iBAAiB,CAAC,UAAU,GAAG;gBAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,GAAG;gBACvC,SAAS,EACP,MAAM,CAAC,SAAS,KAAK,aAAa;oBAChC,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,MAAM,CAAC,SAAS;gBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;aAClB,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC;YACjD,iBAAiB,CAAC,MAAM,GAAG;gBACzB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC;QACJ,CAAC;aAAM,IAAI,aAAa,KAAK,kBAAkB,EAAE,CAAC;YAChD,iBAAiB,CAAC,UAAU,GAAG,IAAI,CAAC;QACtC,CAAC;aAAM,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YACvC,iBAAiB,CAAC,IAAI,GAAG,MAAM,CAAC;QAClC,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,+CAA+C;QAC/C,MAAM,iBAAiB,GAAQ;YAC7B,aAAa,EAAE,MAAM,CAAC,YAAY;YAClC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK;YAC9C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,MAAM;YACjD,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,IAAI,MAAM,CAAC,SAAS;YAC1D,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW;YAC1C,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK;YAClC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;YACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC;QACF,gCAAgC;QAChC,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ;YAC5B,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CACtE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,gCAAgC;QAChC,IAAI,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;YAClC,iBAAiB,CAAC,YAAY,GAAG,EAAE,CAAC;YACpC,iBAAiB,CAAC,YAAY,CAAC,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CACtE,CAAC,WAAgB,EAAE,EAAE,CACnB,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACL,CAAC;QACJ,CAAC;QACD,kCAAkC;QAClC,IAAI,MAAM,EAAE,SAAS;YACnB,iBAAiB,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAa,EAAE,EAAE;gBACnE,MAAM,mBAAmB,GAAQ;oBAC/B,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAiB,EAAE,EAAE;oBACxD,kCAAkC;oBAClC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAC7B;oBACD,gBAAgB,EAAE;wBAChB,wDAAwD;wBACxD,SAAS,EAAE,GAAG,YAAY,CACxB,QAAQ,CAAC,sBAAsB,CAChC,QAAQ,YAAY,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE;wBACzD,OAAO,EAAE,YAAY,CAAC,QAAQ,CAAC,gBAAgB,CAAC;wBAChD,WAAW,EAAE,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC;wBACvD,IAAI,EAAE,GAAG,YAAY,CACnB,QAAQ,CAAC,iBAAiB,CAC3B,QAAQ,YAAY,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE;qBACrD;iBACF,CAAC;gBACF,IAAI,QAAQ,CAAC,MAAM;oBACjB,mBAAmB,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE;wBAC/D,MAAM,iBAAiB,GAAQ;4BAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;4BACjB,KAAK,EAAE,MAAM,CAAC,KAAK;yBACpB,CAAC;wBACF,IAAI,MAAM,CAAC,OAAO;4BAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE;gCAC7D,IAAI,OAAO,MAAM,KAAK,QAAQ;oCAAE,OAAO,MAAM,CAAC;gCAC9C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;oCAC/B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;wCAClB,MAAM,GAAG;4CACP,MAAM,EAAE,MAAM,CAAC,IAAI;4CACnB,GAAG,MAAM,CAAC,MAAM;yCACjB,CAAC;oCACJ,CAAC;oCACD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;wCAC7C,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,KAAK;wCACpC,YAAY,EAAE,SAAS;wCACvB,MAAM,EAAE,MAAM;qCACf,CAAC,CAAC;oCACH,OAAO,iBAAiB,CAAC;gCAC3B,CAAC;4BACH,CAAC,CAAC,CAAC;wBAEL,OAAO,iBAAiB,CAAC;oBAC3B,CAAC,CAAC,CAAC;gBACL,OAAO,mBAAmB,CAAC;YAC7B,CAAC,CAAC,CAAC;QACL,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,yEAAyE;QACzE,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QACzC,MAAM,iBAAiB,GAAQ,EAAE,CAAC;QAClC,iDAAiD;QACjD,iBAAiB,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAC/C,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;YACtE,iBAAiB,CAAC,QAAQ,GAAG,EAAE,CAAC;YAChC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC9B,IAAI;gBACJ,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ;gBACvC,MAAM,EAAE;oBACN,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK;oBACjC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM;iBACpC;gBACD,QAAQ,EAAE;oBACR,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,cAAc;oBAC1C,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe;iBAC7C;aACF,CAAC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QACzC,IAAI,iBAAsB,CAAC;QAC3B,iDAAiD;QACjD,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE,GAAG,MAAM,CAAC;QAC/D,kBAAkB,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACtC,kBAAkB,CAAC,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;QACnD,iBAAiB,GAAG,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAE9C,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,YAAY;YACvB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,2CAA2C;QAC3C,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,WAAW,EAAE,MAAM,CAAC,IAAI;SACzB,CAAC;QACF,IAAI,MAAM,CAAC,QAAQ;YACjB,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAC7D,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,IAAI,MAAM,CAAC,OAAO;YAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAgB,EAAE,EAAE,CAClE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACH,CAAC;QACJ,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CACvD,oBAAoB,CAAC;YACnB,aAAa,EAAE,SAAS;YACxB,YAAY,EAAE,SAAS;YACvB,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,6DAA6D;QAC7D,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;SAAM,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,2CAA2C;QAC3C,MAAM,iBAAiB,GAAQ;YAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;YACjB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,WAAW,EAAE,MAAM,CAAC,IAAI;YACxB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,MAAM,EAAE,MAAM,CAAC,KAAK;YACpB,KAAK,EAAE,MAAM,CAAC,OAAO;SACtB,CAAC;QACF,IAAI,MAAM,CAAC,QAAQ;YACjB,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAC7D,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,OAAO;aAChB,CAAC,CACH,CAAC;QACJ,IAAI,MAAM,CAAC,OAAO;YAChB,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAgB,EAAE,EAAE,CAClE,oBAAoB,CAAC;gBACnB,aAAa,EAAE,YAAY;gBAC3B,YAAY,EAAE,YAAY;gBAC1B,MAAM,EAAE,WAAW;aACpB,CAAC,CACH,CAAC;QACJ,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CACvD,oBAAoB,CAAC;YACnB,aAAa,EAAE,GAAG,IAAI,CAAC,MAAM,KAAK;YAClC,YAAY,EAAE,SAAS;YACvB,MAAM,EAAE,IAAI;SACb,CAAC,CACH,CAAC;QAEF,MAAM,MAAM,GAAG,QAAQ,CAAC;YACtB,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,iBAAiB;SAC1B,CAAC,CAAC;QACH,6DAA6D;QAC7D,sBAAsB;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IACD,8EAA8E;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 66dce587..5fca7213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,36 +1,37 @@ { "name": "doc-detective-common", - "version": "3.6.1-dev.2", + "version": "4.0.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "doc-detective-common", - "version": "3.6.1-dev.2", + "version": "4.0.0-beta.0", "license": "AGPL-3.0-only", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^15.1.3", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", "ajv-keywords": "^5.1.0", - "axios": "^1.13.2", "yaml": "^2.8.2" }, "devDependencies": { - "@types/node": "^22.10.5", + "@apidevtools/json-schema-ref-parser": "^15.2.2", + "@types/node": "^25.2.3", "c8": "^10.1.3", "chai": "^6.2.2", + "esbuild": "^0.27.3", "json-schema-to-typescript": "^15.0.4", "mocha": "^11.7.5", "sinon": "^21.0.1", - "typescript": "^5.7.3" + "typescript": "^5.9.3" } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "15.1.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.1.3.tgz", - "integrity": "sha512-XvEitlOaU8S+hOrMPuGyCjp6vC51K+syUN4HHrSUdSDLLWRWQJYjInU6xlSoRGCVBCfcoHxbRm+yiaYq2yFR5w==", + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.2.2.tgz", + "integrity": "sha512-54fvjSwWiBTdVviiUItOCeyxtPSBmCrSEjlOl8XFEDuYD3lXY1lOBWKim/WJ3i1EYzdGx6rSOjK5KRDMppLI4Q==", + "dev": true, "license": "MIT", "dependencies": { "js-yaml": "^4.1.1" @@ -52,6 +53,448 @@ "node": ">=18" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -263,6 +706,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "peer": true }, "node_modules/@types/lodash": { @@ -273,13 +717,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/ajv": { @@ -362,25 +806,9 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -438,19 +866,6 @@ } } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -526,18 +941,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -589,15 +992,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -608,20 +1002,6 @@ "node": ">=0.3.1" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -635,49 +1015,46 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "node": ">=18" }, - "engines": { - "node": ">= 0.4" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escalade": { @@ -755,25 +1132,6 @@ "flat": "cli.js" } }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -791,31 +1149,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -826,43 +1159,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -884,18 +1180,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -905,45 +1189,6 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1089,6 +1334,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -1206,36 +1452,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -1454,11 +1670,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -1743,9 +1954,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index a09ad78c..804662fa 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,33 @@ { "name": "doc-detective-common", - "version": "3.6.1-dev.2", + "version": "4.0.0-beta.0", "description": "Shared components for Doc Detective projects.", - "main": "dist/index.js", + "type": "module", + "main": "dist/index.cjs", "types": "dist/index.d.ts", "exports": { ".": { - "require": "./dist/index.js", - "import": "./dist/index.mjs", - "types": "./dist/index.d.ts" + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } } }, "scripts": { - "generate:types": "node scripts/generateTypes.js", - "dereferenceSchemas": "node ./src/schemas/dereferenceSchemas.js", - "compile": "tsc && node scripts/createEsmWrapper.js", + "generate:types": "node scripts/generateTypes.cjs", + "dereferenceSchemas": "node ./src/schemas/dereferenceSchemas.cjs", + "compile": "tsc && node scripts/createCjsWrapper.js", "build": "npm run dereferenceSchemas && npm run generate:types && npm run compile", "postbuild": "npm run test", "test": "mocha", "test:coverage": "c8 mocha", "test:coverage:html": "c8 --reporter=html mocha", "test:coverage:check": "c8 check-coverage", - "test:coverage:ratchet": "node scripts/check-coverage-ratchet.js" + "test:coverage:ratchet": "node scripts/check-coverage-ratchet.cjs" }, "repository": { "type": "git", @@ -34,21 +40,21 @@ }, "homepage": "https://github.com/doc-detective/doc-detective-common#readme", "devDependencies": { - "@types/node": "^22.10.5", + "@apidevtools/json-schema-ref-parser": "^15.2.2", + "@types/node": "^25.2.3", "c8": "^10.1.3", + "esbuild": "^0.27.3", "chai": "^6.2.2", "json-schema-to-typescript": "^15.0.4", "mocha": "^11.7.5", "sinon": "^21.0.1", - "typescript": "^5.7.3" + "typescript": "^5.9.3" }, "dependencies": { - "@apidevtools/json-schema-ref-parser": "^15.1.3", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", "ajv-keywords": "^5.1.0", - "axios": "^1.13.2", "yaml": "^2.8.2" } } diff --git a/scripts/check-coverage-ratchet.js b/scripts/check-coverage-ratchet.cjs similarity index 100% rename from scripts/check-coverage-ratchet.js rename to scripts/check-coverage-ratchet.cjs diff --git a/scripts/createCjsWrapper.js b/scripts/createCjsWrapper.js new file mode 100644 index 00000000..9269847c --- /dev/null +++ b/scripts/createCjsWrapper.js @@ -0,0 +1,25 @@ +import { build } from "esbuild"; +import { copyFile } from "fs/promises"; +import { fileURLToPath } from "url"; +import path from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const distDir = path.join(__dirname, "..", "dist"); + +await build({ + entryPoints: [path.join(distDir, "index.js")], + outfile: path.join(distDir, "index.cjs"), + bundle: true, + format: "cjs", + platform: "node", + packages: "external", +}); + +await copyFile( + path.join(distDir, "index.d.ts"), + path.join(distDir, "index.d.cts") +); + +console.log("Created CJS bundle at dist/index.cjs"); +console.log("Copied type definitions to dist/index.d.cts"); diff --git a/scripts/createEsmWrapper.js b/scripts/createEsmWrapper.js deleted file mode 100644 index e7e54c6e..00000000 --- a/scripts/createEsmWrapper.js +++ /dev/null @@ -1,24 +0,0 @@ -const fs = require("fs").promises; -const path = require("path"); - -async function createEsmWrapper() { - const distDir = path.join(__dirname, "..", "dist"); - - // Ensure dist directory exists - await fs.mkdir(distDir, { recursive: true }); - - // Create ESM wrapper that re-exports from CJS - const esmContent = `// ESM wrapper for CommonJS output -import cjsModule from './index.js'; -export const { schemas, validate, transformToSchemaKey, resolvePaths, readFile } = cjsModule; -export default cjsModule; -`; - - await fs.writeFile(path.join(distDir, "index.mjs"), esmContent); - console.log("Created ESM wrapper at dist/index.mjs"); -} - -createEsmWrapper().catch((error) => { - console.error("Failed to create ESM wrapper:", error); - process.exit(1); -}); diff --git a/scripts/generateTypes.js b/scripts/generateTypes.cjs similarity index 100% rename from scripts/generateTypes.js rename to scripts/generateTypes.cjs diff --git a/src/detectTests.ts b/src/detectTests.ts new file mode 100644 index 00000000..b4d8448b --- /dev/null +++ b/src/detectTests.ts @@ -0,0 +1,641 @@ +/** + * Browser-compatible test detection utilities. + * This module provides pure parsing functionality that works with strings/objects, + * without dependencies on Node.js file system or path modules. + */ + +import YAML from "yaml"; +import { validate, transformToSchemaKey } from "./validate.js"; +import { SchemaKey } from "./schemas/index.js"; + +// Web Crypto API compatible UUID generation +/* c8 ignore next 10 - crypto.randomUUID always available in Node.js; fallback is for browsers */ +function generateUUID(): string { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +export interface FileType { + name?: string; + extensions: string[]; + inlineStatements?: { + testStart?: string[]; + testEnd?: string[]; + ignoreStart?: string[]; + ignoreEnd?: string[]; + step?: string[]; + }; + markup?: Array<{ + regex: string[]; + actions?: (string | Record)[]; + batchMatches?: boolean; + }>; + runShell?: Record; +} + +export interface DetectTestsConfig { + detectSteps?: boolean; + origin?: string; + logLevel?: string; + _herettoPathMapping?: Record; +} + +export interface DetectedTest { + testId?: string; + detectSteps?: boolean; + steps: Array>; + [key: string]: any; +} + +export interface DetectTestsInput { + content: string; + filePath: string; + fileType: FileType; + config?: DetectTestsConfig; +} + +/** + * Browser-compatible test detection function. + * Detects tests from content string using specified file type configuration. + * + * This is the main entry point for test detection in Common. + * It works with content strings rather than file paths, making it browser-compatible. + * + * @param input - Detection input + * @param input.content - Content string to parse for tests + * @param input.filePath - File path (for metadata only, not file I/O) + * @param input.fileType - File type configuration with parsing rules + * @param input.config - Optional configuration + * @returns Array of detected tests + * + * @example + * ```typescript + * const tests = await detectTests({ + * content: markdownContent, + * filePath: 'docs/test.md', + * fileType: { extensions: ['md'], markup: [...] }, + * config: { detectSteps: true } + * }); + * ``` + */ +export async function detectTests(input: DetectTestsInput): Promise { + return parseContent({ + config: input.config || {}, + content: input.content, + filePath: input.filePath, + fileType: input.fileType, + }); +} + +/** + * Parses XML-style attributes to an object. + * Example: 'wait=500' becomes { wait: 500 } + * Example: 'testId="myTestId" detectSteps=false' becomes { testId: "myTestId", detectSteps: false } + * Example: 'httpRequest.url="https://example.com"' becomes { httpRequest: { url: "https://example.com" } } + */ +export function parseXmlAttributes({ stringifiedObject }: { stringifiedObject: string }): Record | null { + if (typeof stringifiedObject !== "string") { + return null; + } + + const str = stringifiedObject.trim(); + + // Check if it looks like JSON or YAML - if so, return null to let JSON/YAML parsers handle it + if (str.startsWith("{") || str.startsWith("[")) { + return null; + } + + // Check if it looks like YAML (key: value pattern) + const yamlPattern = /^\w+:\s/; + if (yamlPattern.test(str)) { + return null; + } + if (str.startsWith("-")) { + return null; + } + + // Parse XML-style attributes + const result: Record = {}; + const attrRegex = /([\w.]+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g; + let match; + let hasMatches = false; + + while ((match = attrRegex.exec(str)) !== null) { + hasMatches = true; + const keyPath = match[1]; + let value: any = match[2] !== undefined ? match[2] : match[3] !== undefined ? match[3] : match[4]; + + // Try to parse as boolean + if (value === "true") { + value = true; + } else if (value === "false") { + value = false; + } else if (!isNaN(value) && value !== "") { + value = Number(value); + } + + // Handle dot notation for nested objects + if (keyPath.includes(".")) { + const keys = keyPath.split("."); + let current = result; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key] || typeof current[key] !== "object") { + current[key] = {}; + } + current = current[key]; + } + + current[keys[keys.length - 1]] = value; + } else { + result[keyPath] = value; + } + } + + return hasMatches ? result : null; +} + +/** + * Parses a JSON or YAML object from a string. + */ +export function parseObject({ stringifiedObject }: { stringifiedObject: string }): Record | null { + if (typeof stringifiedObject === "string") { + // First, try to parse as XML attributes + const xmlAttrs = parseXmlAttributes({ stringifiedObject }); + if (xmlAttrs !== null) { + return xmlAttrs; + } + + // Try to parse as JSON + try { + const json = JSON.parse(stringifiedObject); + if (typeof json !== "object" || json === null || Array.isArray(json)) return null; + return json; + } catch (jsonError) { + // JSON parsing failed - check if this looks like escaped JSON + const trimmedString = stringifiedObject.trim(); + const looksLikeEscapedJson = + (trimmedString.startsWith("{") || trimmedString.startsWith("[")) && + trimmedString.includes('\\"'); + + if (looksLikeEscapedJson) { + try { + const stringToParse = JSON.parse('"' + stringifiedObject + '"'); + const result = JSON.parse(stringToParse); + if (typeof result !== "object" || result === null || Array.isArray(result)) return null; + return result; + } catch { + // Fallback to simple quote replacement + try { + const unescaped = stringifiedObject.replace(/\\"/g, '"'); + const result = JSON.parse(unescaped); + if (typeof result !== "object" || result === null || Array.isArray(result)) return null; + return result; + } catch { + // Continue to YAML parsing + } + } + } + + // Try to parse as YAML + try { + const yaml = YAML.parse(stringifiedObject); + if (typeof yaml !== "object" || yaml === null || Array.isArray(yaml)) return null; + return yaml; + } catch (yamlError) { + return null; + } + } + } + return stringifiedObject as any; +} + +/** + * Replaces numeric variables ($0, $1, etc.) in strings and objects with provided values. + */ +export function replaceNumericVariables( + stringOrObjectSource: string | Record, + values: Record +): string | Record | null { + let stringOrObject = JSON.parse(JSON.stringify(stringOrObjectSource)); + + if (typeof stringOrObject !== "string" && typeof stringOrObject !== "object") { + throw new Error("Invalid stringOrObject type"); + } + if (typeof values !== "object") { + throw new Error("Invalid values type"); + } + + if (typeof stringOrObject === "string") { + const matches = stringOrObject.match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + return null; + } else { + stringOrObject = stringOrObject.replace(/\$[0-9]+/g, (variable) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + + if (typeof stringOrObject === "object") { + Object.keys(stringOrObject).forEach((key) => { + if (typeof stringOrObject[key] === "object") { + const result = replaceNumericVariables(stringOrObject[key], values); + /* c8 ignore next 3 - defensive guard: recursive calls on objects can't return null currently */ + if (result === null) { + delete stringOrObject[key]; + } else { + stringOrObject[key] = result; + } + } else if (typeof stringOrObject[key] === "string") { + const matches = stringOrObject[key].match(/\$[0-9]+/g); + if (matches) { + const allExist = matches.every((variable: string) => { + const index = variable.substring(1); + return Object.hasOwn(values, index) && typeof values[index] !== "undefined"; + }); + if (!allExist) { + delete stringOrObject[key]; + } else { + stringOrObject[key] = stringOrObject[key].replace(/\$[0-9]+/g, (variable: string) => { + const index = variable.substring(1); + return values[index]; + }); + } + } + } + }); + } + + return stringOrObject; +} + +/** + * Parses raw test content into an array of structured test objects. + * This is a browser-compatible function that works with strings and doesn't require file system access. + * + * @param options - Options for parsing + * @param options.config - Test configuration object + * @param options.content - Raw file content as a string + * @param options.filePath - Path to the file being parsed (for metadata, not file I/O) + * @param options.fileType - File type definition containing parsing rules + * @returns Array of parsed and validated test objects + */ +export async function parseContent({ + config, + content, + filePath, + fileType, +}: { + config: DetectTestsConfig; + content: string; + filePath: string; + fileType: FileType; +}): Promise { + const statements: Array = []; + const statementTypes = ["testStart", "testEnd", "ignoreStart", "ignoreEnd", "step"]; + + function findTest({ tests, testId }: { tests: DetectedTest[]; testId: string }): DetectedTest { + let test = tests.find((t) => t.testId === testId); + if (!test) { + test = { testId, steps: [] }; + tests.push(test); + } + return test; + } + + // Test for each statement type + statementTypes.forEach((statementType) => { + if ( + typeof fileType.inlineStatements === "undefined" || + typeof fileType.inlineStatements[statementType as keyof typeof fileType.inlineStatements] === "undefined" + ) + return; + + fileType.inlineStatements[statementType as keyof typeof fileType.inlineStatements]!.forEach((statementRegex) => { + let regex: RegExp; + try { + regex = new RegExp(statementRegex, "g"); + } catch { + return; + } + const matches = [...content.matchAll(regex)]; + matches.forEach((match: any) => { + match.type = statementType; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + }); + }); + + if (config.detectSteps && fileType.markup) { + fileType.markup.forEach((markup) => { + markup.regex.forEach((pattern) => { + let regex: RegExp; + try { + regex = new RegExp(pattern, "g"); + } catch { + return; + } + const matches = [...content.matchAll(regex)]; + if (matches.length > 0 && markup.batchMatches) { + const combinedMatch: any = { + 1: matches.map((match) => match[1] || match[0]).join("\n"), + type: "detectedStep", + markup: markup, + sortIndex: Math.min(...matches.map((match) => match.index!)), + }; + statements.push(combinedMatch); + } else if (matches.length > 0) { + matches.forEach((match: any) => { + match.type = "detectedStep"; + match.markup = markup; + match.sortIndex = match[1] ? match.index + match[1].length : match.index; + }); + statements.push(...matches); + } + }); + }); + } + + // Sort statements by index + statements.sort((a, b) => a.sortIndex - b.sortIndex); + + // Process statements into tests and steps + let tests: DetectedTest[] = []; + let testId = generateUUID(); + let ignore = false; + + statements.forEach((statement) => { + let test: DetectedTest | undefined; + let statementContent = ""; + let stepsCleanup = false; + + switch (statement.type) { + case "testStart": { + statementContent = statement[1] || statement[0]; + const parsedTest = parseObject({ stringifiedObject: statementContent }); + if (!parsedTest || typeof parsedTest !== 'object') break; + + test = parsedTest as DetectedTest; + + // If v2 schema, convert to v3 + if (test.id || test.file || test.setup || test.cleanup) { + if (!test.steps) { + test.steps = [{ action: "goTo", url: "https://doc-detective.com" }]; + stepsCleanup = true; + } + const transformed = transformToSchemaKey({ + object: test, + currentSchema: "test_v2" as SchemaKey, + targetSchema: "test_v3" as SchemaKey, + }); + test = transformed as DetectedTest; + if (stepsCleanup && test) { + test.steps = []; + } + } + + if (test.testId) { + testId = test.testId; + } else { + test.testId = testId; + } + + if (test.detectSteps === "false" as any) { + test.detectSteps = false; + } else if (test.detectSteps === "true" as any) { + test.detectSteps = true; + } + if (!test.steps) { + test.steps = []; + } + tests.push(test); + break; + } + + case "testEnd": + testId = generateUUID(); + ignore = false; + break; + + case "ignoreStart": + ignore = true; + break; + + case "ignoreEnd": + ignore = false; + break; + + case "detectedStep": + if (ignore) break; + test = findTest({ tests, testId }); + if (typeof test.detectSteps !== "undefined" && !test.detectSteps) { + break; + } + if (statement?.markup?.actions) { + statement.markup.actions.forEach((action: string | Record) => { + let step: Record = {}; + if (typeof action === "string") { + if (action === "runCode") return; + step[action] = statement[1] || statement[0]; + if (config.origin && (action === "goTo" || action === "checkLink")) { + step[action] = { ...step[action], origin: config.origin }; + } + // Attach sourceIntegration for Heretto + if (action === "screenshot" && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + const screenshotPath = step[action]; + step[action] = { + path: screenshotPath, + sourceIntegration: { + type: "heretto", + integrationName: herettoIntegration, + filePath: screenshotPath, + contentPath: filePath, + }, + }; + } + } + } else { + const replacedStep = replaceNumericVariables(action, statement); + /* c8 ignore next - typeof string check is defensive; object actions always return objects */ + if (!replacedStep || typeof replacedStep === 'string') return; + step = replacedStep; + + // Attach sourceIntegration for Heretto + if (step.screenshot && config._herettoPathMapping) { + const herettoIntegration = findHerettoIntegration(config, filePath); + if (herettoIntegration) { + if (typeof step.screenshot === "string") { + step.screenshot = { path: step.screenshot }; + } else if (typeof step.screenshot === "boolean") { + step.screenshot = {}; + } + step.screenshot.sourceIntegration = { + type: "heretto", + integrationName: herettoIntegration, + filePath: step.screenshot.path || "", + contentPath: filePath, + }; + } + } + } + + // Normalize step field formats + if (step.httpRequest?.request) { + if (typeof step.httpRequest.request.headers === "string") { + try { + const headers: Record = {}; + step.httpRequest.request.headers.split("\n").forEach((header: string) => { + const colonIndex = header.indexOf(":"); + if (colonIndex === -1) return; + const key = header.substring(0, colonIndex).trim(); + const value = header.substring(colonIndex + 1).trim(); + /* c8 ignore next 3 - V8 phantom branch in && short-circuit */ + if (key && value) { + headers[key] = value; + } + }); + step.httpRequest.request.headers = headers; + /* c8 ignore next 2 - string split/forEach can't throw */ + } catch (error) { + } + } + if ( + typeof step.httpRequest.request.body === "string" && + (step.httpRequest.request.body.trim().startsWith("{") || + step.httpRequest.request.body.trim().startsWith("[")) + ) { + try { + step.httpRequest.request.body = JSON.parse(step.httpRequest.request.body); + } catch (error) { + // Ignore parsing errors + } + } + } + + // Validate step + const valid = validate({ + schemaKey: "step_v3" as SchemaKey, + object: step, + addDefaults: false, + }); + if (!valid.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = valid.object; + test!.steps.push(step); + }); + } + break; + + case "step": { + if (ignore) break; + test = findTest({ tests, testId }); + statementContent = statement[1] || statement[0]; + const parsedStep = parseObject({ stringifiedObject: statementContent }); + if (!parsedStep || typeof parsedStep !== 'object') break; + + let step = parsedStep; + const validation = validate({ + schemaKey: "step_v3" as SchemaKey, + object: step, + addDefaults: false, + }); + /* c8 ignore start - V8 phantom branch on if-else/switch-case */ + if (!validation.valid) { + log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`); + return; + } + step = validation.object; + test.steps.push(step); + break; + /* c8 ignore stop */ + } + + /* c8 ignore next 2 - all statement types are handled above */ + default: + break; + } + }); + + // Validate test objects + const validatedTests: DetectedTest[] = []; + tests.forEach((test) => { + const validation = validate({ + schemaKey: "test_v3" as SchemaKey, + object: test, + addDefaults: false, + }); + if (!validation.valid) { + log(config, "warn", `Couldn't convert test in ${filePath} to valid test. Skipping.`); + return; + } + validatedTests.push(validation.object); + }); + + return validatedTests; +} + +/** + * Helper function to find which Heretto integration a file belongs to. + */ +function findHerettoIntegration(config: DetectTestsConfig, filePath: string): string | null { + /* c8 ignore next - callers always check _herettoPathMapping before calling */ + if (!config._herettoPathMapping) return null; + + // Simple string matching since we don't have path.resolve in browser + const normalizedFilePath = filePath.replace(/\\/g, "/"); + + for (const [outputPath, integrationName] of Object.entries(config._herettoPathMapping)) { + const normalizedOutputPath = outputPath.replace(/\\/g, "/"); + if (normalizedFilePath.startsWith(normalizedOutputPath)) { + return integrationName; + } + } + + return null; +} + +/** + * Simple browser-compatible logging function. + */ +export function log(config: DetectTestsConfig, level: string, message: any): void { + const logLevels = ["silent", "error", "warn", "info", "debug"]; + + // Normalize 'warning' to 'warn' for both config and message levels + const configLevel = (config.logLevel || "info") === "warning" ? "warn" : (config.logLevel || "info"); + const normalizedLevel = level === "warning" ? "warn" : level; + + const configLevelIndex = logLevels.indexOf(configLevel); + const messageLevelIndex = logLevels.indexOf(normalizedLevel); + + if (configLevelIndex < 0 || messageLevelIndex < 0) return; + if (messageLevelIndex > configLevelIndex) return; + + // Treat message-level 'silent' as a no-op to avoid calling an undefined console method + if (normalizedLevel === "silent") return; + + if (typeof message === "object") { + console[normalizedLevel as 'error' | 'warn' | 'info' | 'debug'](JSON.stringify(message, null, 2)); + } else { + console[normalizedLevel as 'error' | 'warn' | 'info' | 'debug'](message); + } +} diff --git a/src/files.js b/src/files.js deleted file mode 100644 index 4bb92aae..00000000 --- a/src/files.js +++ /dev/null @@ -1,84 +0,0 @@ -const fs = require("fs"); -const YAML = require("yaml"); -const axios = require("axios"); -const { URL } = require("url"); - -/** - * Reads and parses content from a remote URL or local file path, supporting JSON and YAML formats. - * - * Attempts to parse the file content as JSON first, then YAML. If both parsing attempts fail, returns the raw content as a string. Returns `null` if the file cannot be read. - * - * @param {Object} options - * @param {string} options.fileURLOrPath - The URL or local file path to read. - * @returns {Promise} Parsed object for JSON or YAML files, raw string for other formats, or `null` if reading fails. - * - * @throws {Error} If {@link fileURLOrPath} is missing, not a string, or is an empty string. - */ -async function readFile({ fileURLOrPath }) { - if (!fileURLOrPath) { - throw new Error("fileURLOrPath is required"); - } - if (typeof fileURLOrPath !== "string") { - throw new Error("fileURLOrPath must be a string"); - } - if (fileURLOrPath.trim() === "") { - throw new Error("fileURLOrPath cannot be an empty string"); - } - - let content; - let isRemote = false; - - try { - const parsedURL = new URL(fileURLOrPath); - isRemote = - parsedURL.protocol === "http:" || parsedURL.protocol === "https:"; - } catch (error) { - // Not a valid URL, assume local file path - } - - if (isRemote) { - try { - const response = await axios.get(fileURLOrPath); - content = response.data; - } catch (error) { - console.warn( - `Error reading remote file from ${fileURLOrPath}: ${error.message}` - ); - return null; - } - } else { - try { - content = await fs.promises.readFile(fileURLOrPath, "utf8"); - } catch (error) { - if (error.code === "ENOENT") { - console.warn(`File not found: ${fileURLOrPath}`); - } else { - console.warn(`Error reading file: ${error.message}`); - } - return null; - } - } - - // Parse based on file extension - const ext = fileURLOrPath.split('.').pop().toLowerCase(); - - if (ext === "json") { - try { - return JSON.parse(content); - } catch (error) { - console.warn(`Failed to parse JSON: ${error.message}`); - return content; - } - } else if (ext === "yaml" || ext === "yml") { - try { - return YAML.parse(content); - } catch (error) { - console.warn(`Failed to parse YAML: ${error.message}`); - return content; - } - } else { - return content; - } -} - -module.exports = { readFile }; diff --git a/src/files.ts b/src/files.ts deleted file mode 100644 index 53a0caac..00000000 --- a/src/files.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as fs from "fs"; -import * as YAML from "yaml"; -import axios from "axios"; -import { URL } from "url"; - -export interface ReadFileOptions { - fileURLOrPath: string; -} - -/** - * Reads and parses content from a remote URL or local file path, supporting JSON and YAML formats. - * - * Attempts to parse the file content as JSON first, then YAML. If both parsing attempts fail, returns the raw content as a string. Returns `null` if the file cannot be read. - * - * @param options - Options object - * @param options.fileURLOrPath - The URL or local file path to read. - * @returns Parsed object for JSON or YAML files, raw string for other formats, or `null` if reading fails. - * - * @throws {Error} If {@link fileURLOrPath} is missing, not a string, or is an empty string. - */ -export async function readFile({ fileURLOrPath }: ReadFileOptions): Promise { - if (!fileURLOrPath) { - throw new Error("fileURLOrPath is required"); - } - if (typeof fileURLOrPath !== "string") { - throw new Error("fileURLOrPath must be a string"); - } - if (fileURLOrPath.trim() === "") { - throw new Error("fileURLOrPath cannot be an empty string"); - } - - let content: string; - let isRemote = false; - - try { - const parsedURL = new URL(fileURLOrPath); - isRemote = - parsedURL.protocol === "http:" || parsedURL.protocol === "https:"; - } catch (error) { - // Not a valid URL, assume local file path - } - - if (isRemote) { - try { - const response = await axios.get(fileURLOrPath); - content = response.data; - } catch (error) { - console.warn( - `Error reading remote file from ${fileURLOrPath}: ${(error as Error).message}` - ); - return null; - } - } else { - try { - content = await fs.promises.readFile(fileURLOrPath, "utf8"); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") { - console.warn(`File not found: ${fileURLOrPath}`); - } else { - console.warn(`Error reading file: ${(error as Error).message}`); - } - return null; - } - } - - // Parse based on file extension - const ext = fileURLOrPath.split('.').pop()?.toLowerCase(); - - if (ext === "json") { - try { - return JSON.parse(content); - } catch (error) { - console.warn(`Failed to parse JSON: ${(error as Error).message}`); - return content; - } - } else if (ext === "yaml" || ext === "yml") { - try { - return YAML.parse(content); - } catch (error) { - console.warn(`Failed to parse YAML: ${(error as Error).message}`); - return content; - } - } else { - return content; - } -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index c1f0f0c2..00000000 --- a/src/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const { schemas } = require("./schemas"); -const { validate, transformToSchemaKey } = require("./validate"); -const { resolvePaths } = require("./resolvePaths"); -const { readFile } = require("./files"); - -module.exports = { - schemas, - validate, - resolvePaths, - readFile, - transformToSchemaKey, -}; diff --git a/src/index.ts b/src/index.ts index 7988c299..f25f6e02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -export { schemas, SchemaKey, Schema } from "./schemas"; -export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate"; -export { resolvePaths, ResolvePathsOptions } from "./resolvePaths"; -export { readFile, ReadFileOptions } from "./files"; +export { schemas, SchemaKey, Schema } from "./schemas/index.js"; +export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate.js"; +export { detectTests, DetectTestsInput, DetectedTest, DetectTestsConfig, FileType } from "./detectTests.js"; diff --git a/src/resolvePaths.js b/src/resolvePaths.js deleted file mode 100644 index 794bc4e9..00000000 --- a/src/resolvePaths.js +++ /dev/null @@ -1,253 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const { validate } = require("./validate"); - -exports.resolvePaths = resolvePaths; - -/** - * Convert recognized relative path properties in a config or spec object to absolute paths. - * - * Traverses the provided object (recursing into nested objects and arrays), resolving fields that represent filesystem paths according to the provided config.relativePathBase and reference filePath. On top-level calls the function infers whether the object is a config or spec via schema validation; for nested calls objectType must be provided. - * - * @param {Object} options - Options for path resolution. - * @param {Object} options.config - Configuration containing settings such as `relativePathBase`. - * @param {Object} options.object - The config or spec object whose path properties will be resolved. - * @param {string} options.filePath - Reference file or directory used to resolve relative paths. - * @param {boolean} [options.nested=false] - True when invoked recursively for nested objects. - * @param {string} [options.objectType] - 'config' or 'spec'; required for nested invocations to select which properties to resolve. - * @returns {Object} The same object with applicable path properties converted to absolute paths. - * @throws {Error} If the top-level object matches neither config nor spec schema, or if `objectType` is missing for nested calls. - */ -async function resolvePaths({ - config, - object, - filePath, - nested = false, - objectType, -}) { - // Config properties that contain paths - const configPaths = [ - "input", - "output", - "loadVariables", - "setup", - "cleanup", - "configPath", - "beforeAny", - "afterAll", - "mediaDirectory", - "downloadDirectory", - "descriptionPath", - "path", - ]; - // Spec properties that contain paths - const specPaths = [ - "file", - "path", - "directory", - "before", - "after", - "loadVariables", - "setup", - "cleanup", - "savePath", - "saveDirectory", - "specPath", - "descriptionPath", - "workingDirectory", - ]; - // Spec objects that are configurable by the user and shouldn't be resolved - const specNoResolve = [ - "requestData", - "responseData", - "requestHeaders", - "responseHeaders", - "requestParams", - "responseParams", - ]; - - /** - * Resolves a relative path to an absolute path using a specified base type and reference file path. - * - * @param {string} baseType - Indicates whether to resolve relative to the reference file's directory ("file") or the current working directory ("cwd"). - * @param {string} relativePath - The path to resolve, which may be relative or absolute. - * @param {string} filePath - The reference file or directory path used for resolution. - * @returns {string} The absolute path corresponding to {@link relativePath}. - * - * @remark If {@link relativePath} is already absolute, it is returned unchanged. If {@link filePath} does not exist, its extension is used to infer whether it is a file or directory. - * @remark HTTP and HTTPS URLs are returned unchanged without resolution. - */ - function resolve(baseType, relativePath, filePath) { - // If the path is an http:// or https:// URL, or a heretto: URI, return it - if (relativePath.startsWith("https://") || relativePath.startsWith("http://") || relativePath.startsWith("heretto:")) { - return relativePath; - } - - // If path is already absolute, return it - if (path.isAbsolute(relativePath)) { - return relativePath; - } - - // Check if filePath exists and is a file - const fileExists = fs.existsSync(filePath); - const isFile = fileExists - ? fs.lstatSync(filePath).isFile() - : path.parse(filePath).ext !== ""; - - // Use directory of filePath if it's a file (or looks like one) - const basePath = isFile ? path.dirname(filePath) : filePath; - - // Resolve the path based on the base type - return baseType === "file" - ? path.resolve(basePath, relativePath) - : path.resolve(relativePath); - } - - const relativePathBase = config.relativePathBase; - - let pathProperties; - if (!nested && !objectType) { - // Check if object matches the config schema - const validation = validate({ - schemaKey: "config_v3", - object: { ...object }, - }); - if (validation.valid) { - pathProperties = configPaths; - objectType = "config"; - } else { - // Check if object matches the spec schema - const validation = validate({ - schemaKey: "spec_v3", - object: { ...object }, - }); - if (validation.valid) { - pathProperties = specPaths; - objectType = "spec"; - } else { - throw new Error("Object isn't a valid config or spec."); - } - } - } else if (nested && !objectType) { - // If the object is nested, the object type is required - throw new Error("Object type is required for nested objects."); - } else if (objectType === "config") { - // If the object type is config, use configPaths - pathProperties = configPaths; - } else if (objectType === "spec") { - // If the object type is spec, use specPaths - pathProperties = specPaths; - } - - // If the object is null or empty, return it as is - if (object === null || Object.keys(object).length === 0) { - return object; - } - - for (const property of Object.keys(object)) { - // If the property is an array, recursively call resolvePaths for each item in the array - if (Array.isArray(object[property])) { - for (let i = 0; i < object[property].length; i++) { - const item = object[property][i]; - - // If the item is an object, recursively call resolvePaths to resolve paths within the object - if (typeof item === "object") { - await resolvePaths({ - config: config, - object: item, - filePath: filePath, - nested: true, - objectType: objectType, - }); - } else if ( - typeof item === "string" && - pathProperties.includes(property) - ) { - // Resolve the string path and write it back into the array - const resolved = - property === "path" && - object.directory && - path.isAbsolute(object.directory) - ? resolve(relativePathBase, item, object.directory) - : resolve(relativePathBase, item, filePath); - object[property][i] = resolved; - } - } - } - // If the property is an object, recursively call resolvePaths to resolve paths within the object - else if ( - typeof object[property] === "object" && - ((objectType === "spec" && !specNoResolve.includes(property)) || - objectType === "config") - ) { - // If the property is an object, recursively call resolvePaths to resolve paths within the object - object[property] = await resolvePaths({ - config: config, - object: object[property], - filePath: filePath, - nested: true, - objectType: objectType, - }); - } else if (typeof object[property] === "string") { - // If the property begins with "https://", "http://", or "heretto:", skip it - if ( - object[property].startsWith("https://") || - object[property].startsWith("http://") || - object[property].startsWith("heretto:") - ) { - continue; - } - // Check if it matches any of the path properties and resolve it if it does - if (pathProperties.includes(property)) { - if (property === "path" && object.directory) { - const directory = path.isAbsolute(object.directory) - ? object.directory - : resolve(relativePathBase, object.directory, filePath); - object[property] = resolve( - relativePathBase, - object[property], - directory - ); - } else { - object[property] = resolve( - relativePathBase, - object[property], - filePath - ); - } - } - } - } - return object; -} - -// If called directly, resolve paths in the provided object -/* c8 ignore start */ -if (require.main === module) { - (async () => { - // Example usage - const config = { - relativePathBase: "file", - }; - const object = { - tests: [ - { - steps: [ - { - screenshot: { - path: "file.png", - directory: - "/home/hawkeyexl/Workspaces/doc-detective-common/screenshots", - }, - }, - ], - }, - ], - }; - const filePath = process.cwd(); - - await resolvePaths({ config, object, filePath }); - console.log(JSON.stringify(object, null, 2)); - })(); -} -/* c8 ignore stop */ \ No newline at end of file diff --git a/src/resolvePaths.ts b/src/resolvePaths.ts deleted file mode 100644 index 9cd6c011..00000000 --- a/src/resolvePaths.ts +++ /dev/null @@ -1,233 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { validate } from "./validate"; - -type RelativePathBase = "file" | "cwd"; -type ObjectType = "config" | "spec"; - -export interface ResolvePathsOptions { - config: { relativePathBase: RelativePathBase }; - object: Record; - filePath: string; - nested?: boolean; - objectType?: ObjectType; -} - -/** - * Convert recognized relative path properties in a config or spec object to absolute paths. - * - * Traverses the provided object (recursing into nested objects and arrays), resolving fields that represent filesystem paths according to the provided config.relativePathBase and reference filePath. On top-level calls the function infers whether the object is a config or spec via schema validation; for nested calls objectType must be provided. - * - * @param options - Options for path resolution. - * @param options.config - Configuration containing settings such as `relativePathBase`. - * @param options.object - The config or spec object whose path properties will be resolved. - * @param options.filePath - Reference file or directory used to resolve relative paths. - * @param options.nested - True when invoked recursively for nested objects. - * @param options.objectType - 'config' or 'spec'; required for nested invocations to select which properties to resolve. - * @returns The same object with applicable path properties converted to absolute paths. - * @throws {Error} If the top-level object matches neither config nor spec schema, or if `objectType` is missing for nested calls. - */ -export async function resolvePaths({ - config, - object, - filePath, - nested = false, - objectType, -}: ResolvePathsOptions): Promise> { - // Config properties that contain paths - const configPaths = [ - "input", - "output", - "loadVariables", - "setup", - "cleanup", - "configPath", - "beforeAny", - "afterAll", - "mediaDirectory", - "downloadDirectory", - "descriptionPath", - "path", - ]; - // Spec properties that contain paths - const specPaths = [ - "file", - "path", - "directory", - "before", - "after", - "loadVariables", - "setup", - "cleanup", - "savePath", - "saveDirectory", - "specPath", - "descriptionPath", - "workingDirectory", - ]; - // Spec objects that are configurable by the user and shouldn't be resolved - const specNoResolve = [ - "requestData", - "responseData", - "requestHeaders", - "responseHeaders", - "requestParams", - "responseParams", - ]; - - /** - * Resolves a relative path to an absolute path using a specified base type and reference file path. - * - * @param baseType - Indicates whether to resolve relative to the reference file's directory ("file") or the current working directory ("cwd"). - * @param relativePath - The path to resolve, which may be relative or absolute. - * @param filePath - The reference file or directory path used for resolution. - * @returns The absolute path corresponding to {@link relativePath}. - * - * @remark If {@link relativePath} is already absolute, it is returned unchanged. If {@link filePath} does not exist, its extension is used to infer whether it is a file or directory. - * @remark HTTP and HTTPS URLs are returned unchanged without resolution. - */ - function resolve(baseType: RelativePathBase, relativePath: string, filePath: string): string { - // If the path is an http:// or https:// URL, or a heretto: URI, return it - if (relativePath.startsWith("https://") || relativePath.startsWith("http://") || relativePath.startsWith("heretto:")) { - return relativePath; - } - - // If path is already absolute, return it - if (path.isAbsolute(relativePath)) { - return relativePath; - } - - // Check if filePath exists and is a file - const fileExists = fs.existsSync(filePath); - const isFile = fileExists - ? fs.lstatSync(filePath).isFile() - : path.parse(filePath).ext !== ""; - - // Use directory of filePath if it's a file (or looks like one) - const basePath = isFile ? path.dirname(filePath) : filePath; - - // Resolve the path based on the base type - return baseType === "file" - ? path.resolve(basePath, relativePath) - : path.resolve(relativePath); - } - - const relativePathBase = config.relativePathBase; - - let pathProperties: string[]; - if (!nested && !objectType) { - // Check if object matches the config schema - const validation = validate({ - schemaKey: "config_v3", - object: { ...object }, - }); - if (validation.valid) { - pathProperties = configPaths; - objectType = "config"; - } else { - // Check if object matches the spec schema - const validation = validate({ - schemaKey: "spec_v3", - object: { ...object }, - }); - if (validation.valid) { - pathProperties = specPaths; - objectType = "spec"; - } else { - throw new Error("Object isn't a valid config or spec."); - } - } - } else if (nested && !objectType) { - // If the object is nested, the object type is required - throw new Error("Object type is required for nested objects."); - } else if (objectType === "config") { - // If the object type is config, use configPaths - pathProperties = configPaths; - } else if (objectType === "spec") { - // If the object type is spec, use specPaths - pathProperties = specPaths; - } else { - throw new Error("Invalid objectType"); - } - - // If the object is null or empty, return it as is - if (object === null || Object.keys(object).length === 0) { - return object; - } - - for (const property of Object.keys(object)) { - // If the property is an array, recursively call resolvePaths for each item in the array - if (Array.isArray(object[property])) { - for (let i = 0; i < object[property].length; i++) { - const item = object[property][i]; - - // If the item is an object, recursively call resolvePaths to resolve paths within the object - if (typeof item === "object") { - await resolvePaths({ - config: config, - object: item, - filePath: filePath, - nested: true, - objectType: objectType, - }); - } else if ( - typeof item === "string" && - pathProperties.includes(property) - ) { - // Resolve the string path and write it back into the array - const resolved = - property === "path" && - object.directory && - path.isAbsolute(object.directory) - ? resolve(relativePathBase, item, object.directory) - : resolve(relativePathBase, item, filePath); - object[property][i] = resolved; - } - } - } - // If the property is an object, recursively call resolvePaths to resolve paths within the object - else if ( - typeof object[property] === "object" && - ((objectType === "spec" && !specNoResolve.includes(property)) || - objectType === "config") - ) { - // If the property is an object, recursively call resolvePaths to resolve paths within the object - object[property] = await resolvePaths({ - config: config, - object: object[property], - filePath: filePath, - nested: true, - objectType: objectType, - }); - } else if (typeof object[property] === "string") { - // If the property begins with "https://", "http://", or "heretto:", skip it - if ( - object[property].startsWith("https://") || - object[property].startsWith("http://") || - object[property].startsWith("heretto:") - ) { - continue; - } - // Check if it matches any of the path properties and resolve it if it does - if (pathProperties.includes(property)) { - if (property === "path" && object.directory) { - const directory = path.isAbsolute(object.directory) - ? object.directory - : resolve(relativePathBase, object.directory, filePath); - object[property] = resolve( - relativePathBase, - object[property], - directory - ); - } else { - object[property] = resolve( - relativePathBase, - object[property], - filePath - ); - } - } - } - } - return object; -} diff --git a/src/schemas/dereferenceSchemas.js b/src/schemas/dereferenceSchemas.cjs similarity index 100% rename from src/schemas/dereferenceSchemas.js rename to src/schemas/dereferenceSchemas.cjs diff --git a/src/schemas/index.js b/src/schemas/index.js deleted file mode 100644 index 1a87702f..00000000 --- a/src/schemas/index.js +++ /dev/null @@ -1,6 +0,0 @@ -const schemas = require("./schemas.json"); - -// Exports -exports.schemas = schemas; - -// console.log(schemas); \ No newline at end of file diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 60b5bf5f..aaf6629b 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -1,4 +1,4 @@ -import schemasJson from "./schemas.json"; +import schemasJson from "./schemas.json" with { type: "json" }; export type SchemaKey = keyof typeof schemasJson; export type Schema = (typeof schemasJson)[SchemaKey]; diff --git a/src/validate.js b/src/validate.js deleted file mode 100644 index e2602651..00000000 --- a/src/validate.js +++ /dev/null @@ -1,596 +0,0 @@ -const { schemas } = require("./schemas"); -const Ajv = require("ajv"); -// Ajv extra formats: https://ajv.js.org/packages/ajv-formats.html -const addFormats = require("ajv-formats"); -// Ajv extra keywords: https://ajv.js.org/packages/ajv-keywords.html -const addKeywords = require("ajv-keywords"); -// Ajv custom errors: https://ajv.js.org/packages/ajv-errors.html -const addErrors = require("ajv-errors"); -const { randomUUID } = require("crypto"); - -// Configure base Ajv -const ajv = new Ajv({ - strictSchema: false, - useDefaults: true, - allErrors: true, - allowUnionTypes: true, - coerceTypes: true, -}); - -// Enable `uuid` dynamic default -const def = require("ajv-keywords/dist/definitions/dynamicDefaults"); -def.DEFAULTS.uuid = () => randomUUID; - -// Enhance Ajv -addFormats(ajv); -addKeywords(ajv); -addErrors(ajv); - -// Exports -exports.validate = validate; -exports.transformToSchemaKey = transformToSchemaKey; - -// Add all schemas from `schema` object. -for (const [key, value] of Object.entries(schemas)) { - ajv.addSchema(value, key); -} - -const compatibleSchemas = { - config_v3: ["config_v2"], - context_v3: ["context_v2"], - openApi_v3: ["openApi_v2"], - spec_v3: ["spec_v2"], - step_v3: [ - "checkLink_v2", - "find_v2", - "goTo_v2", - "httpRequest_v2", - "runShell_v2", - "runCode_v2", - "saveScreenshot_v2", - "setVariables_v2", - "startRecording_v2", - "stopRecording_v2", - "typeKeys_v2", - "wait_v2", - ], - test_v3: ["test_v2"], -}; - -/** - * Escapes special characters in a string for safe use in a regular expression pattern. - * - * @param {string} string - The input string to escape. - * @returns {string} The escaped string, safe for use in regular expressions. - */ -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -} - -/** - * Validates an object against a specified JSON schema, supporting backward compatibility and automatic transformation from older schema versions if needed. - * - * If validation against the target schema fails and compatible older schemas are defined, attempts validation against each compatible schema. On a match, transforms the object to the target schema and revalidates. Returns the validation result, any errors, and the (possibly transformed) object. - * - * @param {Object} options - * @param {string} options.schemaKey - The key identifying the target JSON schema. - * @param {Object} options.object - The object to validate. - * @param {boolean} [options.addDefaults=true] - Whether to include default values in the returned object. - * @returns {{ valid: boolean, errors: string, object: Object }} Validation result, error messages, and the validated (and possibly transformed) object. - * - * @throws {Error} If {@link schemaKey} or {@link object} is missing. - */ -function validate({ schemaKey, object, addDefaults = true }) { - if (!schemaKey) { - throw new Error("Schema key is required."); - } - if (!object) { - throw new Error("Object is required."); - } - const result = {}; - let validationObject; - let check = ajv.getSchema(schemaKey); - if (!check) { - result.valid = false; - result.errors = `Schema not found: ${schemaKey}`; - result.object = object; - return result; - } - - // Clone the object to avoid modifying the original object - validationObject = JSON.parse(JSON.stringify(object)); - - // Check if the object is compatible with the schema - result.valid = check(validationObject); - result.errors = ""; - - if (check.errors) { - // Check if the object is compatible with another schema - const compatibleSchemasList = compatibleSchemas[schemaKey]; - if (!compatibleSchemasList) { - result.errors = check.errors - .map( - (error) => - `${error.instancePath} ${error.message} (${JSON.stringify( - error.params - )})` - ) - .join(", "); - result.object = object; - result.valid = false; - return result; - } - const matchedSchemaKey = compatibleSchemasList.find((key) => { - validationObject = JSON.parse(JSON.stringify(object)); - const check = ajv.getSchema(key); - if (check(validationObject)) return key; - }); - if (!matchedSchemaKey) { - result.errors = check.errors - .map( - (error) => - `${error.instancePath} ${error.message} (${JSON.stringify( - error.params - )})` - ) - .join(", "); - result.object = object; - result.valid = false; - return result; - } else { - const transformedObject = transformToSchemaKey({ - currentSchema: matchedSchemaKey, - targetSchema: schemaKey, - object: validationObject, - }); - - result.valid = check(transformedObject); - if (result.valid) { - validationObject = transformedObject; - object = transformedObject; - /* c8 ignore start - Defensive: transformToSchemaKey validates internally, so this is unreachable */ - } else if (check.errors) { - const errors = check.errors.map( - (error) => - `${error.instancePath} ${error.message} (${JSON.stringify( - error.params - )})` - ); - result.errors = errors.join(", "); - return result; - } - /* c8 ignore stop */ - } - } - if (addDefaults) { - result.object = validationObject; - } else { - result.object = object; - } - - return result; -} - -/** - * Transform an object from one schema key to another and return a validated instance of the target schema. - * - * @param {Object} params - Function parameters. - * @param {string} params.currentSchema - Schema key representing the object's current version. - * @param {string} params.targetSchema - Schema key to transform the object into. - * @param {Object} params.object - The source object to transform. - * @returns {Object} The transformed object conforming to the target schema. - * @throws {Error} If transformation between the specified schemas is not supported or if the transformed object fails validation. - */ -function transformToSchemaKey({ - currentSchema = "", - targetSchema = "", - object = {}, -}) { - // Check if the current schema is the same as the target schema - if (currentSchema === targetSchema) { - return object; - } - // Check if the current schema is compatible with the target schema - if (!compatibleSchemas[targetSchema].includes(currentSchema)) { - throw new Error( - `Can't transform from ${currentSchema} to ${targetSchema}.` - ); - } - // Transform the object - if (targetSchema === "step_v3") { - const transformedObject = { - stepId: object.id, - description: object.description, - }; - if (currentSchema === "goTo_v2") { - transformedObject.goTo = { - url: object.url, - origin: object.origin, - }; - } else if (currentSchema === "checkLink_v2") { - transformedObject.checkLink = { - url: object.url, - origin: object.origin, - statusCodes: object.statusCodes, - }; - } else if (currentSchema === "find_v2") { - transformedObject.find = { - selector: object.selector, - elementText: object.matchText, - timeout: object.timeout, - moveTo: object.moveTo, - click: object.click, - type: object.typeKeys, - }; - // Handle typeKeys.delay key change - if (typeof object.typeKeys === "object" && object.typeKeys.keys) { - transformedObject.find.type.inputDelay = object.typeKeys.delay; - delete transformedObject.find.type.delay; - } - transformedObject.variables = {}; - object.setVariables?.forEach((variable) => { - transformedObject.variables[ - variable.name - ] = `extract($$element.text, "${variable.regex}")`; - }); - } else if (currentSchema === "httpRequest_v2") { - transformedObject.httpRequest = { - method: object.method, - url: object.url, - openApi: object.openApi, - request: { - body: object.requestData, - headers: object.requestHeaders, - parameters: object.requestParams, - }, - response: { - body: object.responseData, - headers: object.responseHeaders, - }, - statusCodes: object.statusCodes, - allowAdditionalFields: object.allowAdditionalFields, - timeout: object.timeout, - path: object.savePath, - directory: object.saveDirectory, - maxVariation: object.maxVariation / 100, - overwrite: - object.overwrite === "byVariation" - ? "aboveVariation" - : object.overwrite, - }; - // Handle openApi.requestHeaders key change - if (object.openApi) { - transformedObject.httpRequest.openApi = transformToSchemaKey({ - currentSchema: "openApi_v2", - targetSchema: "openApi_v3", - object: object.openApi, - }); - } - transformedObject.variables = {}; - object.envsFromResponseData?.forEach((variable) => { - transformedObject.variables[ - variable.name - ] = `jq($$response.body, "${variable.jqFilter}")`; - }); - } else if (currentSchema === "runShell_v2") { - transformedObject.runShell = { - command: object.command, - args: object.args, - workingDirectory: object.workingDirectory, - exitCodes: object.exitCodes, - stdio: object.output, - path: object.savePath, - directory: object.saveDirectory, - maxVariation: object.maxVariation / 100, - overwrite: - object.overwrite === "byVariation" - ? "aboveVariation" - : object.overwrite, - timeout: object.timeout, - }; - transformedObject.variables = {}; - object.setVariables?.forEach((variable) => { - transformedObject.variables[ - variable.name - ] = `extract($$stdio.stdout, "${variable.regex}")`; - }); - } else if (currentSchema === "runCode_v2") { - transformedObject.runCode = { - language: object.language, - code: object.code, - args: object.args, - workingDirectory: object.workingDirectory, - exitCodes: object.exitCodes, - stdio: object.output, - path: object.savePath, - directory: object.saveDirectory, - maxVariation: object.maxVariation / 100, - overwrite: - object.overwrite === "byVariation" - ? "aboveVariation" - : object.overwrite, - timeout: object.timeout, - }; - transformedObject.variables = {}; - object?.setVariables?.forEach((variable) => { - transformedObject.variables[ - variable.name - ] = `extract($$stdio.stdout, "${variable.regex}")`; - }); - } else if (currentSchema === "setVariables_v2") { - transformedObject.loadVariables = object.path; - } else if (currentSchema === "typeKeys_v2") { - transformedObject.type = { - keys: object.keys, - inputDelay: object.delay, - }; - } else if (currentSchema === "saveScreenshot_v2") { - transformedObject.screenshot = { - path: object.path, - directory: object.directory, - maxVariation: object.maxVariation / 100, - overwrite: - object.overwrite === "byVariation" - ? "aboveVariation" - : object.overwrite, - crop: object.crop, - }; - } else if (currentSchema === "startRecording_v2") { - transformedObject.record = { - path: object.path, - directory: object.directory, - overwrite: object.overwrite, - }; - } else if (currentSchema === "stopRecording_v2") { - transformedObject.stopRecord = true; - } else if (currentSchema === "wait_v2") { - transformedObject.wait = object; - } - const result = validate({ - schemaKey: "step_v3", - object: transformedObject, - }); - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return result.object; - } else if (targetSchema === "config_v3") { - // Handle config_v2 to config_v3 transformation - const transformedObject = { - loadVariables: object.envVariables, - input: object?.runTests?.input || object.input, - output: object?.runTests?.output || object.output, - recursive: object?.runTests?.recursive || object.recursive, - relativePathBase: object.relativePathBase, - detectSteps: object?.runTests?.detectSteps, - beforeAny: object?.runTests?.setup, - afterAll: object?.runTests?.cleanup, - logLevel: object.logLevel, - telemetry: object.telemetry, - }; - // Handle context transformation - if (object?.runTests?.contexts) - transformedObject.runOn = object.runTests.contexts.map((context) => - transformToSchemaKey({ - currentSchema: "context_v2", - targetSchema: "context_v3", - object: context, - }) - ); - // Handle openApi transformation - if (object?.integrations?.openApi) { - transformedObject.integrations = {}; - transformedObject.integrations.openApi = object.integrations.openApi.map( - (description) => - transformToSchemaKey({ - currentSchema: "openApi_v2", - targetSchema: "openApi_v3", - object: description, - }) - ); - } - // Handle fileTypes transformation - if (object?.fileTypes) - transformedObject.fileTypes = object.fileTypes.map((fileType) => { - const transformedFileType = { - name: fileType.name, - extensions: fileType.extensions.map((extension) => - // Trim leading `.` from extension - extension.replace(/^\./, "") - ), - inlineStatements: { - // Convert strings to regex, escaping special characters - testStart: `${escapeRegExp( - fileType.testStartStatementOpen - )}(.*?)${escapeRegExp(fileType.testStartStatementClose)}`, - testEnd: escapeRegExp(fileType.testEndStatement), - ignoreStart: escapeRegExp(fileType.testIgnoreStatement), - step: `${escapeRegExp( - fileType.stepStatementOpen - )}(.*?)${escapeRegExp(fileType.stepStatementClose)}`, - }, - }; - if (fileType.markup) - transformedFileType.markup = fileType.markup.map((markup) => { - const transformedMarkup = { - name: markup.name, - regex: markup.regex, - }; - if (markup.actions) - transformedMarkup.actions = markup.actions.map((action) => { - if (typeof action === "string") return action; - if (typeof action === "object") { - if (action.params) { - action = { - action: action.name, - ...action.params, - }; - } - const transformedAction = transformToSchemaKey({ - currentSchema: `${action.action}_v2`, - targetSchema: "step_v3", - object: action, - }); - return transformedAction; - } - }); - - return transformedMarkup; - }); - return transformedFileType; - }); - const result = validate({ - schemaKey: "config_v3", - object: transformedObject, - }); - // Defensive: transformation always produces valid config_v3, unreachable - /* c8 ignore next 3 */ - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return result.object; - } else if (targetSchema === "context_v3") { - const transformedObject = {}; - // Handle context_v2 to context_v3 transformation - transformedObject.platforms = object.platforms; - if (object.app?.name) { - const name = object.app.name === "edge" ? "chrome" : object.app?.name; - transformedObject.browsers = []; - transformedObject.browsers.push({ - name, - headless: object.app?.options?.headless, - window: { - width: object.app?.options?.width, - height: object.app?.options?.height, - }, - viewport: { - width: object.app?.options?.viewport_width, - height: object.app?.options?.viewport_height, - }, - }); - } - const result = validate({ - schemaKey: "context_v3", - object: transformedObject, - }); - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return result.object; - } else if (targetSchema === "openApi_v3") { - let transformedObject; - // Handle openApi_v2 to openApi_v3 transformation - const { name, requestHeaders, ...intermediaryObject } = object; - intermediaryObject.name = object.name; - intermediaryObject.headers = object.requestHeaders; - transformedObject = { ...intermediaryObject }; - - const result = validate({ - schemaKey: "openApi_v3", - object: transformedObject, - }); - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return transformedObject; - } else if (targetSchema === "spec_v3") { - // Handle spec_v2 to spec_v3 transformation - const transformedObject = { - specId: object.id, - description: object.description, - contentPath: object.file, - }; - if (object.contexts) - transformedObject.runOn = object.contexts.map((context) => - transformToSchemaKey({ - currentSchema: "context_v2", - targetSchema: "context_v3", - object: context, - }) - ); - if (object.openApi) - transformedObject.openApi = object.openApi.map((description) => - transformToSchemaKey({ - currentSchema: "openApi_v2", - targetSchema: "openApi_v3", - object: description, - }) - ); - transformedObject.tests = object.tests.map((test) => - transformToSchemaKey({ - currentSchema: "test_v2", - targetSchema: "test_v3", - object: test, - }) - ); - - const result = validate({ - schemaKey: "spec_v3", - object: transformedObject, - }); - // Defensive: nested transforms validate; this is unreachable - /* c8 ignore next 3 */ - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return result.object; - } else if (targetSchema === "test_v3") { - // Handle test_v2 to test_v3 transformation - const transformedObject = { - testId: object.id, - description: object.description, - contentPath: object.file, - detectSteps: object.detectSteps, - before: object.setup, - after: object.cleanup, - }; - if (object.contexts) - transformedObject.runOn = object.contexts.map((context) => - transformToSchemaKey({ - currentSchema: "context_v2", - targetSchema: "context_v3", - object: context, - }) - ); - if (object.openApi) - transformedObject.openApi = object.openApi.map((description) => - transformToSchemaKey({ - currentSchema: "openApi_v2", - targetSchema: "openApi_v3", - object: description, - }) - ); - transformedObject.steps = object.steps.map((step) => - transformToSchemaKey({ - currentSchema: `${step.action}_v2`, - targetSchema: "step_v3", - object: step, - }) - ); - - const result = validate({ - schemaKey: "test_v3", - object: transformedObject, - }); - // Defensive: nested transforms validate; this is unreachable - /* c8 ignore next 3 */ - if (!result.valid) { - throw new Error(`Invalid object: ${result.errors}`); - } - return result.object; - } - /* c8 ignore next - Dead code: incompatible schemas throw at line 197-200 */ - return null; -} - -// If called directly, validate an example object -/* c8 ignore start */ -if (require.main === module) { - const example = { - path: "/User/manny/projects/doc-detective/static/images/image.png", - }; - - const result = validate({ schemaKey: "screenshot_v3", object: example }); - console.log(JSON.stringify(result, null, 2)); -} -/* c8 ignore stop */ \ No newline at end of file diff --git a/src/validate.ts b/src/validate.ts index 68e660e3..c81e9b8a 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -1,4 +1,4 @@ -import { schemas, SchemaKey } from "./schemas"; +import { schemas, SchemaKey } from "./schemas/index.js"; import Ajv, { ValidateFunction } from "ajv"; // Ajv extra formats: https://ajv.js.org/packages/ajv-formats.html import addFormats from "ajv-formats"; @@ -6,9 +6,23 @@ import addFormats from "ajv-formats"; import addKeywords from "ajv-keywords"; // Ajv custom errors: https://ajv.js.org/packages/ajv-errors.html import addErrors from "ajv-errors"; -import { randomUUID } from "crypto"; +import dynamicDefaultsDef from "ajv-keywords/dist/definitions/dynamicDefaults.js"; + +// Browser-compatible UUID function +/* c8 ignore next 10 - crypto.randomUUID always available in Node.js; fallback is for browsers */ +function getRandomUUID(): string { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} // Configure base Ajv +// @ts-expect-error - CJS/ESM interop: Ajv constructor is callable at runtime const ajv = new Ajv({ strictSchema: false, useDefaults: true, @@ -18,13 +32,15 @@ const ajv = new Ajv({ }); // Enable `uuid` dynamic default -// @ts-ignore - ajv-keywords has incomplete types for dynamicDefaults -const def = require("ajv-keywords/dist/definitions/dynamicDefaults"); -def.DEFAULTS.uuid = () => randomUUID; +// @ts-expect-error - CJS/ESM interop: dynamicDefaultsDef.DEFAULTS exists at runtime +dynamicDefaultsDef.DEFAULTS.uuid = (_args: any) => getRandomUUID; // Enhance Ajv +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime addFormats(ajv); +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime addKeywords(ajv); +// @ts-expect-error - CJS/ESM interop: ajv plugin functions are callable at runtime addErrors(ajv); // Add all schemas from `schema` object. diff --git a/test/detectTests.test.js b/test/detectTests.test.js new file mode 100644 index 00000000..8d14d010 --- /dev/null +++ b/test/detectTests.test.js @@ -0,0 +1,1572 @@ +import { expect } from "chai"; +import { detectTests } from "../dist/index.js"; +import { + parseContent, + parseXmlAttributes, + parseObject, + replaceNumericVariables, + log, +} from "../dist/detectTests.js"; + + describe("detectTests module", function () { + // Standard markdown file type for testing + const markdownFileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + testEnd: [""], + ignoreStart: [""], + ignoreEnd: [""], + step: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: ["checkLink"], + }, + ], + }; + + // Minimal file type with no markup or inline statements + const minimalFileType = { + extensions: ["txt"], + }; + // ========== parseXmlAttributes ========== + describe("parseXmlAttributes", function () { + it("should return null for non-string input", function () { + expect(parseXmlAttributes({ stringifiedObject: 123 })).to.be.null; + expect(parseXmlAttributes({ stringifiedObject: null })).to.be.null; + expect(parseXmlAttributes({ stringifiedObject: undefined })).to.be.null; + }); + + it("should return null for JSON-like input starting with {", function () { + expect( + parseXmlAttributes({ stringifiedObject: '{"key": "value"}' }) + ).to.be.null; + }); + + it("should return null for JSON-like input starting with [", function () { + expect( + parseXmlAttributes({ stringifiedObject: '[1, 2, 3]' }) + ).to.be.null; + }); + + it("should return null for YAML-like input (key: value)", function () { + expect( + parseXmlAttributes({ stringifiedObject: "key: value" }) + ).to.be.null; + }); + + it("should return null for input starting with -", function () { + expect( + parseXmlAttributes({ stringifiedObject: "- item" }) + ).to.be.null; + }); + + it("should return null when no matches found", function () { + expect( + parseXmlAttributes({ stringifiedObject: "just plain text" }) + ).to.be.null; + }); + + it("should parse simple key=value pairs", function () { + const result = parseXmlAttributes({ stringifiedObject: 'name=hello' }); + expect(result).to.deep.equal({ name: "hello" }); + }); + + it("should parse double-quoted values", function () { + const result = parseXmlAttributes({ + stringifiedObject: 'testId="myTest"', + }); + expect(result).to.deep.equal({ testId: "myTest" }); + }); + + it("should parse single-quoted values", function () { + const result = parseXmlAttributes({ + stringifiedObject: "testId='myTest'", + }); + expect(result).to.deep.equal({ testId: "myTest" }); + }); + + it("should parse boolean true value", function () { + const result = parseXmlAttributes({ + stringifiedObject: "detectSteps=true", + }); + expect(result).to.deep.equal({ detectSteps: true }); + }); + + it("should parse boolean false value", function () { + const result = parseXmlAttributes({ + stringifiedObject: "detectSteps=false", + }); + expect(result).to.deep.equal({ detectSteps: false }); + }); + + it("should parse numeric values", function () { + const result = parseXmlAttributes({ + stringifiedObject: "wait=500", + }); + expect(result).to.deep.equal({ wait: 500 }); + }); + + it("should handle dot notation for nested objects", function () { + const result = parseXmlAttributes({ + stringifiedObject: 'httpRequest.url="https://example.com"', + }); + expect(result).to.deep.equal({ + httpRequest: { url: "https://example.com" }, + }); + }); + + it("should handle multiple attributes", function () { + const result = parseXmlAttributes({ + stringifiedObject: 'testId="myTest" detectSteps=false wait=500', + }); + expect(result).to.deep.equal({ + testId: "myTest", + detectSteps: false, + wait: 500, + }); + }); + + it("should handle deep dot notation", function () { + const result = parseXmlAttributes({ + stringifiedObject: 'a.b.c="deep"', + }); + expect(result).to.deep.equal({ a: { b: { c: "deep" } } }); + }); + + it("should overwrite non-object intermediate key in dot notation", function () { + // Tests branch where current[key] exists but is not an object + const result = parseXmlAttributes({ + stringifiedObject: 'a=1 a.b="deep"', + }); + // The second attribute overwrites a=1 with a={b:"deep"} + expect(result).to.deep.equal({ a: { b: "deep" } }); + }); + }); + + // ========== parseObject ========== + describe("parseObject", function () { + it("should parse XML attributes first", function () { + const result = parseObject({ + stringifiedObject: 'key="value"', + }); + expect(result).to.deep.equal({ key: "value" }); + }); + + it("should parse valid JSON", function () { + const result = parseObject({ + stringifiedObject: '{"goTo": {"url": "https://example.com"}}', + }); + expect(result).to.deep.equal({ + goTo: { url: "https://example.com" }, + }); + }); + + it("should parse escaped JSON (double-encoded)", function () { + const escaped = '{\\"key\\": \\"value\\"}'; + const result = parseObject({ stringifiedObject: escaped }); + expect(result).to.deep.equal({ key: "value" }); + }); + + it("should parse YAML as fallback", function () { + const result = parseObject({ + stringifiedObject: "goTo:\n url: https://example.com", + }); + expect(result).to.deep.equal({ + goTo: { url: "https://example.com" }, + }); + }); + + it("should return null for invalid JSON and YAML", function () { + const result = parseObject({ + stringifiedObject: "{invalid: json: yaml: :::}", + }); + expect(result).to.be.null; + }); + + it("should return non-string input as-is", function () { + const obj = { key: "value" }; + const result = parseObject({ stringifiedObject: obj }); + expect(result).to.deep.equal(obj); + }); + + it("should handle escaped JSON where double-parse fails but simple unescape succeeds", function () { + // Input: {\"key\": \"val\\ue\"} - double parse creates \u escape that needs 4 hex digits, fails + // Simple unescape: {"key": "val\\ue"} - \\u is literal backslash+u, valid JSON + const input = '{\\"key\\": \\"val\\\\ue\\"}'; + const result = parseObject({ stringifiedObject: input }); + expect(result).to.deep.equal({ key: "val\\ue" }); + }); + + it("should return null for escaped JSON that double-parses to array", function () { + // Input looks like escaped JSON array: [\"a\"] + // Double-parse: JSON.parse('"[\"a\"]"') -> '["a"]' -> JSON.parse -> ["a"] (array) + const input = '[\\"a\\"]'; + const result = parseObject({ stringifiedObject: input }); + expect(result).to.be.null; + }); + + it("should return null for escaped JSON that simple-unescapes to array", function () { + // Input: [\"val\\ue\"] - double parse fails on \u escape, simple unescape yields ["val\\ue"] (array) + const input = '[\\"val\\\\ue\\"]'; + const result = parseObject({ stringifiedObject: input }); + expect(result).to.be.null; + }); + + it("should handle escaped JSON where both parse attempts fail", function () { + // Input looks like escaped JSON but neither parse succeeds + const input = '{\\"broken json\\"}'; + const result = parseObject({ stringifiedObject: input }); + // Falls through to YAML parsing + expect(result).to.not.be.undefined; + }); + + it("should return null for JSON array", function () { + const result = parseObject({ stringifiedObject: "[1, 2, 3]" }); + expect(result).to.be.null; + }); + + it("should return null for JSON primitive", function () { + const result = parseObject({ stringifiedObject: '"just a string"' }); + expect(result).to.be.null; + }); + + it("should return null for YAML array", function () { + const result = parseObject({ stringifiedObject: "- item1\n- item2" }); + expect(result).to.be.null; + }); + }); + + // ========== replaceNumericVariables ========== + describe("replaceNumericVariables", function () { + it("should replace $0, $1 in strings", function () { + const result = replaceNumericVariables("Hello $0 and $1", { + 0: "world", + 1: "everyone", + }); + expect(result).to.equal("Hello world and everyone"); + }); + + it("should return null when not all variables exist in string", function () { + const result = replaceNumericVariables("Hello $0 and $1", { + 0: "world", + }); + expect(result).to.be.null; + }); + + it("should replace variables in object values", function () { + const result = replaceNumericVariables( + { url: "https://$0.com", name: "$1" }, + { 0: "example", 1: "test" } + ); + expect(result).to.deep.equal({ + url: "https://example.com", + name: "test", + }); + }); + + it("should handle nested objects recursively", function () { + const result = replaceNumericVariables( + { outer: { inner: "$0" } }, + { 0: "value" } + ); + expect(result).to.deep.equal({ outer: { inner: "value" } }); + }); + + it("should delete nested object keys when variables don't exist", function () { + const result = replaceNumericVariables( + { outer: { inner: "$1" }, keep: "$0" }, + { 0: "found" } + ); + // Inner key is deleted, leaving outer as empty object + expect(result).to.deep.equal({ outer: {}, keep: "found" }); + }); + + it("should delete object keys when variables don't exist", function () { + const result = replaceNumericVariables( + { url: "$0", missing: "$1" }, + { 0: "found" } + ); + expect(result).to.deep.equal({ url: "found" }); + expect(result).to.not.have.property("missing"); + }); + + it("should return string without variables unchanged", function () { + const result = replaceNumericVariables("no variables", { 0: "x" }); + expect(result).to.equal("no variables"); + }); + + it("should return object without variables unchanged", function () { + const result = replaceNumericVariables( + { key: "no vars" }, + { 0: "x" } + ); + expect(result).to.deep.equal({ key: "no vars" }); + }); + + it("should throw on invalid stringOrObject type", function () { + expect(() => replaceNumericVariables(123, { 0: "x" })).to.throw( + "Invalid stringOrObject type" + ); + }); + + it("should throw on invalid values type", function () { + expect(() => replaceNumericVariables("test", "not-object")).to.throw( + "Invalid values type" + ); + }); + }); + + // ========== log ========== + describe("log", function () { + let originalConsole; + + beforeEach(function () { + originalConsole = { + error: console.error, + warn: console.warn, + info: console.info, + debug: console.debug, + }; + }); + + afterEach(function () { + console.error = originalConsole.error; + console.warn = originalConsole.warn; + console.info = originalConsole.info; + console.debug = originalConsole.debug; + }); + + it("should log string messages at info level", function () { + let logged = null; + console.info = (msg) => { + logged = msg; + }; + log({ logLevel: "info" }, "info", "test message"); + expect(logged).to.equal("test message"); + }); + + it("should log object messages as JSON", function () { + let logged = null; + console.info = (msg) => { + logged = msg; + }; + log({ logLevel: "info" }, "info", { key: "value" }); + expect(logged).to.equal(JSON.stringify({ key: "value" }, null, 2)); + }); + + it("should suppress messages when config level is silent", function () { + let logged = false; + console.info = () => { + logged = true; + }; + log({ logLevel: "silent" }, "info", "test"); + expect(logged).to.be.false; + }); + + it("should suppress messages above config level", function () { + let logged = false; + console.debug = () => { + logged = true; + }; + log({ logLevel: "info" }, "debug", "test"); + expect(logged).to.be.false; + }); + + it("should normalize warning to warn", function () { + let logged = null; + console.warn = (msg) => { + logged = msg; + }; + log({ logLevel: "debug" }, "warning", "warn msg"); + expect(logged).to.equal("warn msg"); + }); + + it("should normalize config logLevel 'warning' to 'warn'", function () { + let logged = null; + console.warn = (msg) => { logged = msg; }; + log({ logLevel: "warning" }, "warn", "config warning test"); + expect(logged).to.equal("config warning test"); + }); + + it("should not log when message level is silent", function () { + let logged = false; + console.error = () => { logged = true; }; + console.warn = () => { logged = true; }; + console.info = () => { logged = true; }; + console.debug = () => { logged = true; }; + log({ logLevel: "debug" }, "silent", "should not appear"); + expect(logged).to.be.false; + }); + + it("should return early for invalid config level", function () { + let logged = false; + console.info = () => { + logged = true; + }; + log({ logLevel: "invalid" }, "info", "test"); + expect(logged).to.be.false; + }); + + it("should return early for invalid message level", function () { + let logged = false; + console.info = () => { + logged = true; + }; + log({ logLevel: "info" }, "invalid", "test"); + expect(logged).to.be.false; + }); + + it("should default to info level when logLevel not specified", function () { + let logged = null; + console.info = (msg) => { + logged = msg; + }; + log({}, "info", "test default"); + expect(logged).to.equal("test default"); + }); + + it("should log error level messages", function () { + let logged = null; + console.error = (msg) => { + logged = msg; + }; + log({ logLevel: "error" }, "error", "error msg"); + expect(logged).to.equal("error msg"); + }); + }); + + // ========== parseContent ========== + describe("parseContent", function () { + it("should return empty array for empty content", async function () { + const result = await parseContent({ + config: {}, + content: "", + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should return empty array for content without test statements", async function () { + const result = await parseContent({ + config: {}, + content: "Just some regular text", + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should detect a test with testStart statement", async function () { + const content = ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + expect(result[0].steps[0].goTo.url).to.equal("https://example.com"); + }); + + it("should handle testEnd resetting testId", async function () { + const content = + '\n' + + "\n" + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(2); + expect(result[0].testId).to.not.equal(result[1].testId); + }); + + it("should parse step inline statements", async function () { + const content = + '\n' + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should skip invalid step statements", async function () { + const content = + '\n' + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + // Only the original valid step, invalid step is skipped + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should skip step when parseObject returns null", async function () { + const content = + '\n' + + ""; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should detect markup steps when detectSteps is true", async function () { + // Use object-style action that produces valid v3 step + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + testEnd: [""], + step: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: [{ checkLink: { url: "$2" } }], + }, + ], + }; + const content = + '\n' + + "[Click me](https://example.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps.length).to.be.greaterThan(0); + }); + + it("should NOT detect markup steps when detectSteps is not set", async function () { + const content = + '\n' + + "[Click me](https://other.com)"; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + // Only the inline step, no markup-detected steps + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should skip detected steps when test has detectSteps=false", async function () { + const content = + '\n' + + "[Click me](https://other.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + // Only the inline step, markup steps skipped due to detectSteps=false + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should normalize detectSteps string 'false' to boolean", async function () { + // Use JSON format so detectSteps stays as string "false" (XML attrs auto-convert) + const content = + ''; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].detectSteps).to.be.false; + }); + + it("should normalize detectSteps string 'true' to boolean", async function () { + // Use JSON format so detectSteps stays as string "true" (XML attrs auto-convert) + const content = + ''; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].detectSteps).to.be.true; + }); + + it("should handle testStart with unparseable content", async function () { + const content = ""; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + // empty capture group -> parseObject gets empty string -> YAML parses to null -> breaks + expect(result).to.be.an("array"); + }); + + it("should initialize empty steps array when test has no steps property", async function () { + // Test object without steps property triggers !test.steps branch + const content = + '\n' + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.be.an("array").with.lengthOf(1); + }); + + it("should handle v2 test schema conversion", async function () { + const content = + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].testId).to.equal("my-test"); + }); + + it("should handle v2 test schema with stepsCleanup", async function () { + const content = + '\n' + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].testId).to.equal("my-test"); + expect(result[0].steps).to.be.an("array").with.lengthOf(1); + }); + + it("should preserve existing testId from test definition", async function () { + const content = + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].testId).to.equal("custom-id"); + }); + + it("should add origin to goTo steps from config (string action)", async function () { + // String actions with origin produce invalid v3 steps (spread chars). + // This test verifies the code path runs even though the step is filtered. + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + testEnd: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: ["goTo"], + }, + ], + }; + const content = + '\n' + + "[Go here](/path)"; + const result = await parseContent({ + config: { detectSteps: true, origin: "https://origin.com" }, + content, + filePath: "test.md", + fileType, + }); + // Test survives because of the seeded step; markup goTo step is invalid v3 and filtered + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should add origin to checkLink steps from config (string action)", async function () { + // String actions with origin produce invalid v3 steps. + // This test exercises the code path even though the step is filtered. + const content = + '\n' + + "[Link](https://other.com)"; + const result = await parseContent({ + config: { detectSteps: true, origin: "https://origin.com" }, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + // Test survives because of seeded step; markup checkLink with origin is invalid v3 + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should skip runCode actions in markup", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + step: [""], + }, + markup: [ + { + regex: ["```(\\w+)\\n([\\s\\S]*?)```"], + actions: ["runCode"], + }, + ], + }; + const content = + '\n' + + "```javascript\nconsole.log('hi');\n```"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + // Only the seeded step, runCode from markup is skipped + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle batchMatches mode in markup", async function () { + // batchMatches combines multiple regex matches into one statement. + // String actions produce invalid v3 steps, so test with a seeded step. + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: ["checkLink"], + batchMatches: true, + }, + ], + }; + const content = + '\n' + + "[Link1](https://a.com)\n" + + "[Link2](https://b.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + // Seeded step survives; batch checkLink string step is invalid v3 + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle markup with object actions and variable replacement", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: [{ checkLink: { url: "$2" } }], + }, + ], + }; + const content = + '\n' + + "[Link](https://example.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + expect(result[0].steps[0].checkLink.url).to.equal("https://example.com"); + }); + + it("should handle screenshot action with Heretto integration", async function () { + // String screenshot action produces invalid v3 step even with Heretto. + // This test exercises the Heretto code path for coverage. + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["!\\[([^\\]]*?)\\]\\(([^)]+)\\)"], + actions: ["screenshot"], + }, + ], + }; + const content = + '\n' + + "![alt](images/test.png)"; + const result = await parseContent({ + config: { + detectSteps: true, + _herettoPathMapping: { "docs/": "my-integration" }, + }, + content, + filePath: "docs/test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + // Seeded step survives; screenshot step with Heretto is invalid v3 + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle screenshot with object action and Heretto integration (string screenshot)", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["!\\[([^\\]]*?)\\]\\(([^)]+)\\)"], + actions: [{ screenshot: "$2" }], + }, + ], + }; + const content = + '\n' + + "![alt](images/test.png)"; + const result = await parseContent({ + config: { + detectSteps: true, + _herettoPathMapping: { "docs/": "my-integration" }, + }, + content, + filePath: "docs/test.md", + fileType, + }); + // Test survives via seeded step; screenshot step may or may not pass v3 validation + expect(result).to.have.lengthOf(1); + }); + + it("should handle screenshot with object action and Heretto integration (boolean screenshot)", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["!\\[([^\\]]*?)\\]\\(([^)]+)\\)"], + actions: [{ screenshot: true }], + }, + ], + }; + const content = + '\n' + + "![alt](images/test.png)"; + const result = await parseContent({ + config: { + detectSteps: true, + _herettoPathMapping: { "docs/": "my-integration" }, + }, + content, + filePath: "docs/test.md", + fileType, + }); + // Test survives via seeded step + expect(result).to.have.lengthOf(1); + }); + + it("should skip Heretto integration when file not in mapping", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["!\\[([^\\]]*?)\\]\\(([^)]+)\\)"], + actions: ["screenshot"], + }, + ], + }; + const content = + '\n' + + "![alt](images/test.png)"; + const result = await parseContent({ + config: { + detectSteps: true, + _herettoPathMapping: { "other/": "my-integration" }, + }, + content, + filePath: "docs/test.md", + fileType, + }); + // Test survives via seeded step; screenshot without Heretto is invalid v3 + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle fileType with no inlineStatements", async function () { + const result = await parseContent({ + config: {}, + content: "Some content", + filePath: "test.txt", + fileType: minimalFileType, + }); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should handle fileType with partial inlineStatements", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + // No testEnd, step, etc. + }, + }; + const content = ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should create implicit test for step without testStart", async function () { + const content = ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle httpRequest step with string headers", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "GET", + request: { + headers: "Content-Type: application/json\nAuthorization: Bearer token", + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should handle httpRequest step with invalid JSON body string", async function () { + // Tests the catch block when JSON.parse fails on body + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "POST", + request: { + body: "{ not valid json }", + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + // Test survives via seeded step; httpRequest body parsing fails gracefully + expect(result).to.have.lengthOf(1); + }); + + it("should handle httpRequest step with JSON body string", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "POST", + request: { + body: '{"key": "value"}', + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should skip replaceNumericVariables returning null", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: [{ checkLink: { url: "$5" } }], + }, + ], + }; + const content = + '\n' + + "[Link](https://other.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + // $5 doesn't match any capture group, so markup step is skipped; only seeded step remains + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should skip invalid string action names", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + actions: ["$1"], + }, + ], + }; + const content = + '\n' + + "[Link](https://other.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + // Test survives via seeded step; "$1" as action name produces invalid step + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle invalid test validation (skips test)", async function () { + // Create a test that passes initial parsing but fails test_v3 validation + const content = + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + // Should be filtered out by validation + expect(result).to.be.an("array"); + }); + + it("should skip detected steps inside ignore block", async function () { + const content = + '\n' + + "\n" + + "[Ignored Link](https://ignored.com)\n" + + ""; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + // Only the step from the test definition; the detected link inside ignore block is skipped + expect(result[0].steps).to.have.lengthOf(1); + expect(result[0].steps[0].goTo.url).to.equal("https://example.com"); + }); + + it("should skip inline steps inside ignore block", async function () { + const content = + '\n' + + "\n" + + '\n' + + ""; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(1); + // Only the step from the test definition; the inline step inside ignore block is skipped + expect(result[0].steps).to.have.lengthOf(1); + expect(result[0].steps[0].goTo.url).to.equal("https://example.com"); + }); + + it("should handle ignoreStart and ignoreEnd", async function () { + const content = + "\n" + + '\n' + + ""; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType: markdownFileType, + }); + // The test is still parsed since ignore only affects detected steps in the original + expect(result).to.be.an("array"); + }); + + it("should handle markup with no actions property", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["\\[([^\\]]+)\\]\\(([^)]+)\\)"], + // No actions + }, + ], + }; + const content = + '\n' + + "[Link](https://other.com)"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + // Only the seeded step; markup with no actions adds nothing + expect(result[0].steps).to.have.lengthOf(1); + }); + + it("should handle Windows-style backslash paths in Heretto mapping", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["!\\[([^\\]]*?)\\]\\(([^)]+)\\)"], + actions: ["screenshot"], + }, + ], + }; + const content = + '\n' + + "![alt](images/test.png)"; + const result = await parseContent({ + config: { + detectSteps: true, + _herettoPathMapping: { "docs\\output\\": "my-integration" }, + }, + content, + filePath: "docs\\output\\test.md", + fileType, + }); + // Test survives via seeded step; screenshot with Heretto is invalid v3 + expect(result).to.have.lengthOf(1); + expect(result[0].steps).to.have.lengthOf(1); + }); + it("should handle markup regex with no capture groups (batchMatches fallback)", async function () { + // Tests match[1] || match[0] fallback and match[1] ? ... : match.index branches + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["LINK:\\S+"], + actions: [{ checkLink: { url: "$0" } }], + batchMatches: true, + }, + ], + }; + const content = + '\n' + + "LINK:a.com LINK:b.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should handle markup regex with no capture group 1 (non-batch)", async function () { + // Tests match[1] falsy branch in non-batch markup + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["LINK:\\S+"], + actions: [{ checkLink: { url: "$0" } }], + }, + ], + }; + const content = + '\n' + + "LINK:test.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should handle string action using statement[0] when statement[1] is falsy", async function () { + // Tests statement[1] || statement[0] fallback for string actions + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["LINK:\\S+"], + actions: ["checkLink"], + }, + ], + }; + const content = + '\n' + + "LINK:test.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + // Test survives via seeded step; string checkLink step is invalid v3 + expect(result).to.have.lengthOf(1); + }); + + it("should handle httpRequest headers with lines missing colons", async function () { + // Tests colonIndex === -1 return branch + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "GET", + request: { + headers: "no-colon-header\nContent-Type: application/json", + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should skip httpRequest headers with empty value after colon", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "GET", + request: { + headers: "EmptyVal:\nContent-Type: application/json", + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should handle httpRequest with array-starting JSON body", async function () { + // Tests the startsWith("[") branch + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["API:\\s*(\\S+)"], + actions: [ + { + httpRequest: { + url: "$1", + method: "POST", + request: { + body: '[1, 2, 3]', + }, + response: {}, + statusCodes: [200], + }, + }, + ], + }, + ], + }; + const content = + '\n' + + "API: https://api.example.com"; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + expect(result).to.have.lengthOf(1); + }); + + it("should handle inline step with no capture group 1", async function () { + // Tests statement[1] || statement[0] fallback for inline step + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + // Step regex without capture group + step: [''], + }, + }; + const content = + '\n' + + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType, + }); + // The step regex matches the literal string; statement[0] is the full match + expect(result).to.be.an("array"); + }); + + it("should handle malformed regex in inlineStatements gracefully", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + step: ["[invalid(regex"], + }, + }; + const content = + ''; + const result = await parseContent({ + config: {}, + content, + filePath: "test.md", + fileType, + }); + // Malformed regex is skipped; test still processes normally + expect(result).to.have.lengthOf(1); + }); + + it("should handle malformed regex in markup gracefully", async function () { + const fileType = { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + markup: [ + { + regex: ["[invalid(regex"], + actions: ["checkLink"], + }, + ], + }; + const content = + ''; + const result = await parseContent({ + config: { detectSteps: true }, + content, + filePath: "test.md", + fileType, + }); + // Malformed regex is skipped; test still processes normally + expect(result).to.have.lengthOf(1); + }); + }); + + // ========== detectTests ========== + describe("detectTests", function () { + it("should call parseContent with default config", async function () { + const result = await detectTests({ + content: "", + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should pass config through to parseContent", async function () { + const content = + '\n' + + "[Link](https://other.com)"; + const result = await detectTests({ + content, + filePath: "test.md", + fileType: markdownFileType, + config: { detectSteps: true }, + }); + expect(result).to.have.lengthOf(1); + // At least the seeded step survives + expect(result[0].steps.length).to.be.greaterThan(0); + }); + + it("should detect multiple tests in content", async function () { + const content = + '\n' + + "\n" + + '\n' + + ""; + const result = await detectTests({ + content, + filePath: "test.md", + fileType: markdownFileType, + }); + expect(result).to.have.lengthOf(2); + }); + }); + }); diff --git a/test/files.test.js b/test/files.test.js deleted file mode 100644 index 9a72ca5b..00000000 --- a/test/files.test.js +++ /dev/null @@ -1,222 +0,0 @@ -const sinon = require("sinon"); -const axios = require("axios"); -const fs = require("fs"); -const { readFile } = require("../dist/files"); - -(async () => { - const { expect } = await import("chai"); - - describe("readFile", function () { - let axiosGetStub; - let fsReadFileStub; - - beforeEach(function () { - axiosGetStub = sinon.stub(axios, "get"); - fsReadFileStub = sinon.stub(fs.promises, "readFile"); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe("input validation", function () { - it("should throw error when fileURLOrPath is missing", async function () { - try { - await readFile({}); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("fileURLOrPath is required"); - } - }); - - it("should throw error when fileURLOrPath is undefined", async function () { - try { - await readFile({ fileURLOrPath: undefined }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("fileURLOrPath is required"); - } - }); - - it("should throw error when fileURLOrPath is not a string", async function () { - try { - await readFile({ fileURLOrPath: 123 }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("fileURLOrPath must be a string"); - } - }); - - it("should throw error when fileURLOrPath is an object", async function () { - try { - await readFile({ fileURLOrPath: { path: "/test" } }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("fileURLOrPath must be a string"); - } - }); - - it("should throw error when fileURLOrPath is an empty string", async function () { - try { - await readFile({ fileURLOrPath: "" }); - expect.fail("Should have thrown an error"); - } catch (error) { - // Empty string is falsy, so first check catches it - expect(error.message).to.equal("fileURLOrPath is required"); - } - }); - - it("should throw error when fileURLOrPath is whitespace only", async function () { - try { - await readFile({ fileURLOrPath: " " }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("fileURLOrPath cannot be an empty string"); - } - }); - }); - - describe("remote file reading", function () { - it("should read a remote JSON file", async function () { - const fileURL = "http://example.com/file.json"; - const fileContent = '{"key": "value"}'; - axiosGetStub.resolves({ data: fileContent }); - - const result = await readFile({ fileURLOrPath: fileURL }); - - expect(result).to.deep.equal({ key: "value" }); - expect(axiosGetStub.calledOnceWith(fileURL)).to.be.true; - }); - - it("should read a remote YAML file", async function () { - const fileURL = "http://example.com/file.yaml"; - const fileContent = "key: value"; - axiosGetStub.resolves({ data: fileContent }); - - const result = await readFile({ fileURLOrPath: fileURL }); - - expect(result).to.deep.equal({ key: "value" }); - expect(axiosGetStub.calledOnceWith(fileURL)).to.be.true; - }); - - it("should read a remote file via https", async function () { - const fileURL = "https://example.com/file.json"; - const fileContent = '{"key": "value"}'; - axiosGetStub.resolves({ data: fileContent }); - - const result = await readFile({ fileURLOrPath: fileURL }); - - expect(result).to.deep.equal({ key: "value" }); - expect(axiosGetStub.calledOnceWith(fileURL)).to.be.true; - }); - - it("should return null if remote file cannot be read", async function () { - const fileURL = "http://example.com/file.json"; - axiosGetStub.rejects(new Error("Network error")); - - const result = await readFile({ fileURLOrPath: fileURL }); - - expect(result).to.be.null; - expect(axiosGetStub.calledOnceWith(fileURL)).to.be.true; - }); - }); - - describe("local file reading", function () { - it("should read a local JSON file", async function () { - const filePath = "/path/to/file.json"; - const fileContent = '{"key": "value"}'; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.deep.equal({ key: "value" }); - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - - it("should read a local YAML file with .yaml extension", async function () { - const filePath = "/path/to/file.yaml"; - const fileContent = "key: value"; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.deep.equal({ key: "value" }); - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - - it("should read a local YAML file with .yml extension", async function () { - const filePath = "/path/to/file.yml"; - const fileContent = "key: value"; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.deep.equal({ key: "value" }); - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - - it("should return raw content for non-JSON/YAML files", async function () { - const filePath = "/path/to/file.txt"; - const fileContent = "plain text content"; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.equal(fileContent); - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - - it("should return null if local file cannot be found", async function () { - const filePath = "/path/to/nonexistent.json"; - fsReadFileStub.rejects({ code: "ENOENT" }); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.be.null; - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - - it("should return null if local file read error occurs", async function () { - const filePath = "/path/to/file.json"; - fsReadFileStub.rejects(new Error("Read error")); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.be.null; - expect(fsReadFileStub.calledOnceWith(filePath, "utf8")).to.be.true; - }); - }); - - describe("parse error handling", function () { - it("should return raw content when JSON parsing fails", async function () { - const filePath = "/path/to/file.json"; - const fileContent = "not valid json {"; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.equal(fileContent); - }); - - it("should return raw content when YAML parsing fails", async function () { - const filePath = "/path/to/file.yaml"; - const fileContent = "invalid: yaml: content: ["; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.equal(fileContent); - }); - - it("should return raw content when .yml parsing fails", async function () { - const filePath = "/path/to/file.yml"; - const fileContent = "invalid: yaml: content: ["; - fsReadFileStub.resolves(fileContent); - - const result = await readFile({ fileURLOrPath: filePath }); - - expect(result).to.equal(fileContent); - }); - }); - }); -})(); diff --git a/test/integration.test.js b/test/integration.test.js index a0b80138..16cc4d74 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -3,10 +3,15 @@ * These tests verify that the package can be consumed via both module systems. */ -(async () => { - const { expect } = await import("chai"); - const path = require("path"); - const { execSync } = require("child_process"); +import { expect } from "chai"; +import { createRequire } from "module"; +import { fileURLToPath } from "url"; +import path from "path"; +import fs from "fs"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const require = createRequire(import.meta.url); describe("Module Integration Tests", function () { // Increase timeout for spawning node processes @@ -14,34 +19,24 @@ describe("CommonJS (require)", function () { it("should export validate function via require", function () { - const { validate } = require("../dist/index.js"); + const { validate } = require("../dist/index.cjs"); expect(typeof validate).to.equal("function"); }); it("should export transformToSchemaKey function via require", function () { - const { transformToSchemaKey } = require("../dist/index.js"); + const { transformToSchemaKey } = require("../dist/index.cjs"); expect(typeof transformToSchemaKey).to.equal("function"); }); - it("should export resolvePaths function via require", function () { - const { resolvePaths } = require("../dist/index.js"); - expect(typeof resolvePaths).to.equal("function"); - }); - - it("should export readFile function via require", function () { - const { readFile } = require("../dist/index.js"); - expect(typeof readFile).to.equal("function"); - }); - it("should export schemas object via require", function () { - const { schemas } = require("../dist/index.js"); + const { schemas } = require("../dist/index.cjs"); expect(typeof schemas).to.equal("object"); expect(schemas).to.have.property("step_v3"); expect(schemas).to.have.property("config_v3"); }); it("should validate a step using CJS imports", function () { - const { validate } = require("../dist/index.js"); + const { validate } = require("../dist/index.cjs"); const result = validate({ schemaKey: "step_v3", object: { @@ -52,44 +47,55 @@ expect(result.object.goTo.url).to.equal("https://example.com"); }); + it("should export detectTests function via require", function () { + const { detectTests } = require("../dist/index.cjs"); + expect(typeof detectTests).to.equal("function"); + }); + + it("should detect tests using CJS imports", async function () { + const { detectTests } = require("../dist/index.cjs"); + const result = await detectTests({ + content: '', + filePath: "test.md", + fileType: { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + }, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps[0].goTo.url).to.equal("https://example.com"); + }); + it("should work with default export via require", function () { - const docDetectiveCommon = require("../dist/index.js"); + const docDetectiveCommon = require("../dist/index.cjs"); expect(typeof docDetectiveCommon.validate).to.equal("function"); expect(typeof docDetectiveCommon.schemas).to.equal("object"); + expect(typeof docDetectiveCommon.detectTests).to.equal("function"); }); }); describe("ESM (import)", function () { it("should export validate function via ESM import", async function () { - // Use dynamic import to test ESM wrapper - const module = await import("../dist/index.mjs"); + const module = await import("../dist/index.js"); expect(typeof module.validate).to.equal("function"); }); it("should export transformToSchemaKey function via ESM import", async function () { - const module = await import("../dist/index.mjs"); + const module = await import("../dist/index.js"); expect(typeof module.transformToSchemaKey).to.equal("function"); }); - it("should export resolvePaths function via ESM import", async function () { - const module = await import("../dist/index.mjs"); - expect(typeof module.resolvePaths).to.equal("function"); - }); - - it("should export readFile function via ESM import", async function () { - const module = await import("../dist/index.mjs"); - expect(typeof module.readFile).to.equal("function"); - }); - it("should export schemas object via ESM import", async function () { - const module = await import("../dist/index.mjs"); + const module = await import("../dist/index.js"); expect(typeof module.schemas).to.equal("object"); expect(module.schemas).to.have.property("step_v3"); expect(module.schemas).to.have.property("config_v3"); }); it("should validate a step using ESM imports", async function () { - const { validate } = await import("../dist/index.mjs"); + const { validate } = await import("../dist/index.js"); const result = validate({ schemaKey: "step_v3", object: { @@ -100,17 +106,32 @@ expect(result.object.goTo.url).to.equal("https://example.com"); }); - it("should have default export via ESM import", async function () { - const module = await import("../dist/index.mjs"); - expect(module.default).to.exist; - expect(typeof module.default.validate).to.equal("function"); + it("should export detectTests function via ESM import", async function () { + const module = await import("../dist/index.js"); + expect(typeof module.detectTests).to.equal("function"); + }); + + it("should detect tests using ESM imports", async function () { + const { detectTests } = await import("../dist/index.js"); + const result = await detectTests({ + content: '', + filePath: "test.md", + fileType: { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + }, + }); + expect(result).to.have.lengthOf(1); + expect(result[0].steps[0].goTo.url).to.equal("https://example.com"); }); }); describe("Cross-module compatibility", function () { it("should produce identical validation results from CJS and ESM", async function () { - const cjsModule = require("../dist/index.js"); - const esmModule = await import("../dist/index.mjs"); + const cjsModule = require("../dist/index.cjs"); + const esmModule = await import("../dist/index.js"); const testObject = { goTo: { url: "https://example.com" }, @@ -131,9 +152,32 @@ expect(cjsResult.object.goTo).to.deep.equal(esmResult.object.goTo); }); + it("should produce identical detectTests results from CJS and ESM", async function () { + const cjsModule = require("../dist/index.cjs"); + const esmModule = await import("../dist/index.js"); + + const testInput = { + content: '', + filePath: "test.md", + fileType: { + extensions: ["md"], + inlineStatements: { + testStart: [""], + }, + }, + }; + + const cjsResult = await cjsModule.detectTests(structuredClone(testInput)); + const esmResult = await esmModule.detectTests(structuredClone(testInput)); + + expect(cjsResult).to.have.lengthOf(1); + expect(esmResult).to.have.lengthOf(1); + expect(cjsResult[0].steps[0].goTo.url).to.equal(esmResult[0].steps[0].goTo.url); + }); + it("should have the same schema keys in CJS and ESM exports", async function () { - const cjsModule = require("../dist/index.js"); - const esmModule = await import("../dist/index.mjs"); + const cjsModule = require("../dist/index.cjs"); + const esmModule = await import("../dist/index.js"); const cjsSchemaKeys = Object.keys(cjsModule.schemas).sort(); const esmSchemaKeys = Object.keys(esmModule.schemas).sort(); @@ -144,38 +188,40 @@ describe("TypeScript type definitions", function () { it("should have type definition file for main export", function () { - const fs = require("fs"); const dtsPath = path.join(__dirname, "..", "dist", "index.d.ts"); expect(fs.existsSync(dtsPath)).to.be.true; }); + it("should have CJS type definition file", function () { + const dtsPath = path.join(__dirname, "..", "dist", "index.d.cts"); + expect(fs.existsSync(dtsPath)).to.be.true; + }); + it("should have type definitions for validate module", function () { - const fs = require("fs"); const dtsPath = path.join(__dirname, "..", "dist", "validate.d.ts"); expect(fs.existsSync(dtsPath)).to.be.true; }); - it("should have type definitions for files module", function () { - const fs = require("fs"); - const dtsPath = path.join(__dirname, "..", "dist", "files.d.ts"); + it("should have type definitions for detectTests module", function () { + const dtsPath = path.join(__dirname, "..", "dist", "detectTests.d.ts"); expect(fs.existsSync(dtsPath)).to.be.true; }); - it("should have type definitions for resolvePaths module", function () { - const fs = require("fs"); - const dtsPath = path.join(__dirname, "..", "dist", "resolvePaths.d.ts"); - expect(fs.existsSync(dtsPath)).to.be.true; + it("should export detectTests types in type definitions", function () { + const dtsPath = path.join(__dirname, "..", "dist", "detectTests.d.ts"); + const content = fs.readFileSync(dtsPath, "utf8"); + expect(content).to.include("detectTests"); + expect(content).to.include("DetectTestsInput"); + expect(content).to.include("FileType"); }); it("should export ValidateOptions interface in type definitions", function () { - const fs = require("fs"); const dtsPath = path.join(__dirname, "..", "dist", "validate.d.ts"); const content = fs.readFileSync(dtsPath, "utf8"); expect(content).to.include("ValidateOptions"); }); it("should export ValidateResult interface in type definitions", function () { - const fs = require("fs"); const dtsPath = path.join(__dirname, "..", "dist", "validate.d.ts"); const content = fs.readFileSync(dtsPath, "utf8"); expect(content).to.include("ValidateResult"); @@ -184,32 +230,47 @@ describe("Package exports field verification", function () { it("should have correct main entry in package.json", function () { - const packageJson = require("../package.json"); - expect(packageJson.main).to.equal("dist/index.js"); + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); + expect(packageJson.main).to.equal("dist/index.cjs"); }); it("should have correct types entry in package.json", function () { - const packageJson = require("../package.json"); + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); expect(packageJson.types).to.equal("dist/index.d.ts"); }); it("should have correct exports.require in package.json", function () { - const packageJson = require("../package.json"); + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); expect(packageJson.exports["."]).to.have.property("require"); - expect(packageJson.exports["."].require).to.equal("./dist/index.js"); + expect(packageJson.exports["."].require.default).to.equal("./dist/index.cjs"); }); it("should have correct exports.import in package.json", function () { - const packageJson = require("../package.json"); + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); expect(packageJson.exports["."]).to.have.property("import"); - expect(packageJson.exports["."].import).to.equal("./dist/index.mjs"); + expect(packageJson.exports["."].import.default).to.equal("./dist/index.js"); + }); + + it("should have correct exports.import.types in package.json", function () { + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); + expect(packageJson.exports["."].import.types).to.equal("./dist/index.d.ts"); }); - it("should have correct exports.types in package.json", function () { - const packageJson = require("../package.json"); - expect(packageJson.exports["."]).to.have.property("types"); - expect(packageJson.exports["."].types).to.equal("./dist/index.d.ts"); + it("should have correct exports.require.types in package.json", function () { + const packageJson = JSON.parse( + fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8") + ); + expect(packageJson.exports["."].require.types).to.equal("./dist/index.d.cts"); }); }); }); -})(); diff --git a/test/resolvePaths.test.js b/test/resolvePaths.test.js deleted file mode 100644 index e30f2b8c..00000000 --- a/test/resolvePaths.test.js +++ /dev/null @@ -1,642 +0,0 @@ -const sinon = require("sinon"); -const fs = require("fs"); -const path = require("path"); - -(async () => { - const { expect } = await import("chai"); - const { resolvePaths } = require("../dist/resolvePaths"); - - describe("resolvePaths", function () { - const mockFilePath = "/home/user/project/config.json"; - const cwd = process.cwd(); - - describe("config object resolution (using nested/objectType)", function () { - it("should resolve relative paths with relativePathBase='file'", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "./input", - output: "./output", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal(path.resolve("/home/user/project", "./input")); - expect(result.output).to.equal(path.resolve("/home/user/project", "./output")); - }); - - it("should resolve relative paths with relativePathBase='cwd'", async function () { - const config = { relativePathBase: "cwd" }; - const object = { - input: "./input", - output: "./output", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal(path.resolve(cwd, "./input")); - expect(result.output).to.equal(path.resolve(cwd, "./output")); - }); - - it("should not modify absolute paths", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "/absolute/path/to/input", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal("/absolute/path/to/input"); - }); - - it("should not modify http:// URLs", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "http://example.com/input.json", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal("http://example.com/input.json"); - }); - - it("should not modify https:// URLs", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "https://example.com/input.json", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal("https://example.com/input.json"); - }); - - it("should not modify heretto: URIs", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "heretto:some-identifier", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.input).to.equal("heretto:some-identifier"); - }); - - it("should resolve loadVariables path", async function () { - const config = { relativePathBase: "file" }; - const object = { - loadVariables: "./vars.json", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.loadVariables).to.equal(path.resolve("/home/user/project", "./vars.json")); - }); - - it("should resolve mediaDirectory path", async function () { - const config = { relativePathBase: "file" }; - const object = { - mediaDirectory: "./media", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.mediaDirectory).to.equal(path.resolve("/home/user/project", "./media")); - }); - - it("should resolve beforeAny and afterAll paths", async function () { - const config = { relativePathBase: "file" }; - const object = { - beforeAny: "./setup.js", - afterAll: "./cleanup.js", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result.beforeAny).to.equal(path.resolve("/home/user/project", "./setup.js")); - expect(result.afterAll).to.equal(path.resolve("/home/user/project", "./cleanup.js")); - }); - }); - - describe("spec object resolution (using nested/objectType)", function () { - it("should resolve file path in spec object", async function () { - const config = { relativePathBase: "file" }; - const object = { - file: "./test.md", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.file).to.equal(path.resolve("/home/user/project", "./test.md")); - }); - - it("should resolve path relative to directory when directory is absolute", async function () { - const config = { relativePathBase: "file" }; - const object = { - directory: "/absolute/dir", - path: "file.png", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.path).to.equal(path.resolve("/absolute/dir", "file.png")); - }); - - it("should resolve path relative to resolved directory when directory is relative", async function () { - const config = { relativePathBase: "file" }; - const object = { - directory: "./screenshots", - path: "file.png", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - const expectedDir = path.resolve("/home/user/project", "./screenshots"); - expect(result.directory).to.equal(expectedDir); - expect(result.path).to.equal(path.resolve(expectedDir, "file.png")); - }); - - it("should resolve before and after paths in spec", async function () { - const config = { relativePathBase: "file" }; - const object = { - before: "./before.js", - after: "./after.js", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.before).to.equal(path.resolve("/home/user/project", "./before.js")); - expect(result.after).to.equal(path.resolve("/home/user/project", "./after.js")); - }); - - it("should resolve workingDirectory path", async function () { - const config = { relativePathBase: "file" }; - const object = { - workingDirectory: "./work", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.workingDirectory).to.equal(path.resolve("/home/user/project", "./work")); - }); - - it("should not resolve requestData objects", async function () { - const config = { relativePathBase: "file" }; - const object = { - requestData: { - path: "./should-not-resolve.json", - }, - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.requestData.path).to.equal("./should-not-resolve.json"); - }); - - it("should not resolve responseData objects", async function () { - const config = { relativePathBase: "file" }; - const object = { - responseData: { - file: "./should-not-resolve.json", - }, - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.responseData.file).to.equal("./should-not-resolve.json"); - }); - - it("should not resolve requestHeaders objects", async function () { - const config = { relativePathBase: "file" }; - const object = { - requestHeaders: { - path: "./should-not-resolve.json", - }, - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.requestHeaders.path).to.equal("./should-not-resolve.json"); - }); - }); - - describe("nested object resolution", function () { - it("should resolve paths in nested objects", async function () { - const config = { relativePathBase: "file" }; - const object = { - tests: [ - { - steps: [ - { - screenshot: { - path: "screenshot.png", - directory: "./screenshots", - }, - }, - ], - }, - ], - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - const expectedDir = path.resolve("/home/user/project", "./screenshots"); - expect(result.tests[0].steps[0].screenshot.directory).to.equal(expectedDir); - }); - - it("should resolve paths in arrays of strings", async function () { - const config = { relativePathBase: "file" }; - const object = { - before: ["./setup1.js", "./setup2.js"], - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.before[0]).to.equal(path.resolve("/home/user/project", "./setup1.js")); - expect(result.before[1]).to.equal(path.resolve("/home/user/project", "./setup2.js")); - }); - - it("should resolve paths in arrays relative to directory", async function () { - const config = { relativePathBase: "file" }; - const object = { - directory: "/absolute/screenshots", - path: ["file1.png", "file2.png"], - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.path[0]).to.equal(path.resolve("/absolute/screenshots", "file1.png")); - expect(result.path[1]).to.equal(path.resolve("/absolute/screenshots", "file2.png")); - }); - - it("should handle URLs in arrays by returning them unchanged", async function () { - const config = { relativePathBase: "file" }; - const object = { - before: ["https://example.com/setup.js", "http://example.com/setup2.js", "heretto:setup3"], - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - // URLs should remain unchanged - expect(result.before[0]).to.equal("https://example.com/setup.js"); - expect(result.before[1]).to.equal("http://example.com/setup2.js"); - expect(result.before[2]).to.equal("heretto:setup3"); - }); - }); - - describe("error handling", function () { - it("should throw error for invalid object (not config or spec)", async function () { - const config = { relativePathBase: "file" }; - const object = { - someRandomProperty: "value", - }; - - try { - await resolvePaths({ config, object, filePath: mockFilePath }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("Object isn't a valid config or spec."); - } - }); - - it("should throw error for nested object without objectType", async function () { - const config = { relativePathBase: "file" }; - const object = { - someProp: "value", - }; - - try { - await resolvePaths({ config, object, filePath: mockFilePath, nested: true }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("Object type is required for nested objects."); - } - }); - - it("should throw error for invalid objectType", async function () { - const config = { relativePathBase: "file" }; - const object = { - someProp: "value", - }; - - try { - await resolvePaths({ config, object, filePath: mockFilePath, nested: true, objectType: "invalid" }); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal("Invalid objectType"); - } - }); - }); - - describe("null and empty object handling", function () { - it("should return null object as is", async function () { - const config = { relativePathBase: "file" }; - const object = null; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result).to.be.null; - }); - - it("should return empty object as is", async function () { - const config = { relativePathBase: "file" }; - const object = {}; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "config", - }); - - expect(result).to.deep.equal({}); - }); - }); - - describe("filePath handling", function () { - it("should handle filePath that is a directory", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "./input", - }; - const dirPath = "/home/user/project"; - - // Stub fs.existsSync and fs.lstatSync for directory - const existsStub = sinon.stub(fs, "existsSync").returns(true); - const lstatStub = sinon.stub(fs, "lstatSync").returns({ isFile: () => false }); - - try { - const result = await resolvePaths({ - config, - object, - filePath: dirPath, - nested: true, - objectType: "config", - }); - expect(result.input).to.equal(path.resolve(dirPath, "./input")); - } finally { - existsStub.restore(); - lstatStub.restore(); - } - }); - - it("should infer directory from path without extension when path doesn't exist", async function () { - const config = { relativePathBase: "file" }; - const object = { - input: "./input", - }; - // Path with no extension - should be treated as directory - const dirPath = "/home/user/project/somedir"; - - // Stub fs.existsSync to return false - const existsStub = sinon.stub(fs, "existsSync").returns(false); - - try { - const result = await resolvePaths({ - config, - object, - filePath: dirPath, - nested: true, - objectType: "config", - }); - expect(result.input).to.equal(path.resolve(dirPath, "./input")); - } finally { - existsStub.restore(); - } - }); - }); - - describe("auto-detection of object type", function () { - it("should auto-detect config_v3 object type and resolve paths", async function () { - const config = { relativePathBase: "file" }; - // A valid config_v3 object (with valid properties) - const object = { - input: "./input", - output: "./output", - recursive: true, - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - // No nested or objectType - should auto-detect - }); - - expect(result.input).to.equal(path.resolve("/home/user/project", "./input")); - expect(result.output).to.equal(path.resolve("/home/user/project", "./output")); - }); - - it("should auto-detect spec_v3 object type and resolve paths", async function () { - const config = { relativePathBase: "file" }; - // A valid spec_v3 object - const object = { - tests: [ - { - steps: [ - { - goTo: "https://example.com" - } - ] - } - ] - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - // No nested or objectType - should auto-detect as spec - }); - - // Object should be returned (even if no paths to resolve) - expect(result).to.deep.include({ tests: object.tests }); - }); - }); - - describe("URL handling in resolve helper", function () { - it("should return http:// URLs unchanged from resolve helper", async function () { - const config = { relativePathBase: "file" }; - const object = { - file: "http://example.com/file.md", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.file).to.equal("http://example.com/file.md"); - }); - - it("should return https:// URLs unchanged from resolve helper", async function () { - const config = { relativePathBase: "file" }; - const object = { - file: "https://example.com/file.md", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.file).to.equal("https://example.com/file.md"); - }); - - it("should return heretto: URIs unchanged from resolve helper", async function () { - const config = { relativePathBase: "file" }; - const object = { - file: "heretto:some-id", - }; - - const result = await resolvePaths({ - config, - object, - filePath: mockFilePath, - nested: true, - objectType: "spec", - }); - - expect(result.file).to.equal("heretto:some-id"); - }); - }); - }); -})(); diff --git a/test/schema.test.js b/test/schema.test.js index f4411bd6..c5717680 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -1,5 +1,5 @@ -const { validate, schemas } = require("../dist/index"); -const assert = require("assert"); +import { validate, schemas } from "../dist/index.js"; +import assert from "assert"; // Loop through JSON schemas for (const [key, value] of Object.entries(schemas)) { diff --git a/test/validate.test.js b/test/validate.test.js index 8ecdc1f3..1c989ae9 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -1,6 +1,5 @@ -(async () => { - const { expect } = await import("chai"); - const { validate, transformToSchemaKey } = require("../dist/validate"); +import { expect } from "chai"; +import { validate, transformToSchemaKey } from "../dist/validate.js"; describe("validate", function () { describe("input validation", function () { @@ -204,7 +203,7 @@ describe("same schema transformation", function () { it("should return object unchanged when currentSchema equals targetSchema", function () { const object = { goTo: { url: "https://example.com" } }; - + const result = transformToSchemaKey({ currentSchema: "step_v3", targetSchema: "step_v3", @@ -1103,4 +1102,3 @@ }); }); }); -})(); diff --git a/tsconfig.json b/tsconfig.json index dda928f9..a7d97684 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "lib": ["ES2022"], "declaration": true, "declarationMap": true, @@ -18,5 +18,5 @@ "types": ["node"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "src/schemas/dereferenceSchemas.js"] + "exclude": ["node_modules", "dist", "test", "src/schemas/dereferenceSchemas.cjs"] }