@inamul_hasan/vouch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1389 -0
- package/dist/index.js.map +1 -0
- package/examples/demo.vch +18 -0
- package/examples/real_login.vch +21 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands.ts","../src/cli/runner.ts","../src/types/index.ts","../src/engine/prompts.ts","../src/engine/providers/openai.ts","../src/engine/providers/base.ts","../src/engine/providers/anthropic.ts","../src/engine/providers/google.ts","../src/engine/providers/ollama.ts","../src/engine/vision.ts","../src/browser/controller.ts","../src/actions/coordinator.ts","../src/parser/vch-parser.ts","../src/reporter/json-reporter.ts","../src/cli/init.ts","../src/cli/interactive.ts","../src/cli/logger.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport * as fs from \"node:fs\";\nimport type { VouchConfig } from \"../types/index.js\";\nimport { loadConfig, runTestFile } from \"./runner.js\";\nimport { initProject } from \"./init.js\";\nimport { runInteractiveMenu } from \"./interactive.js\";\nimport { createLogger, loadLoggerDeps } from \"./logger.js\";\n\nasync function ensureConfigExists() {\n if (!fs.existsSync(\"vouch.config.json\")) {\n await initProject(true);\n }\n}\n\nexport function createCLI(): Command {\n const program = new Command();\n\n program\n .name(\"vouch\")\n .description(\"πΈοΈ Vouch β Zero-selector, vision-driven web automation\")\n .version(\"1.0.0\");\n\n program.action(async () => {\n await ensureConfigExists();\n await runInteractiveMenu();\n });\n\n program\n .command(\"run\")\n .description(\"Execute a .vch file\")\n .argument(\"<file>\", \"Path to the .vch file\")\n .option(\n \"-p, --provider <provider>\",\n \"AI provider (openai, anthropic, google, ollama)\",\n )\n .option(\"-m, --model <model>\", \"AI model identifier\")\n .option(\"-k, --api-key <key>\", \"API key for the AI provider\")\n .option(\"--base-url <url>\", \"Base URL override\")\n .option(\"--headless\", \"Run browser in headless mode\")\n .option(\"--no-headless\", \"Run browser in headed mode\")\n .option(\"--retries <n>\", \"Max retries per step\", parseInt)\n .option(\"--viewport <WxH>\", \"Viewport size (e.g., 1280x800)\")\n .option(\"--no-report\", \"Skip JSON report generation\")\n .option(\"--report-dir <dir>\", \"JSON report output directory\")\n .action(async (file: string, options: Record<string, unknown>) => {\n await ensureConfigExists();\n await loadLoggerDeps();\n const chalk = await import(\"chalk\");\n const boxen = await import(\"boxen\");\n\n const c = chalk.default ?? chalk;\n const b = boxen.default ?? boxen;\n\n console.log(\n b(c.bold.white(\"πΈοΈ VOUCH\") + c.dim(\" β Vision-Driven Automation\"), {\n padding: { top: 0, bottom: 0, left: 2, right: 2 },\n borderStyle: \"round\",\n borderColor: \"white\",\n dimBorder: true,\n }),\n );\n\n const overrides: Partial<VouchConfig> = {};\n if (options.provider)\n overrides.provider = options.provider as VouchConfig[\"provider\"];\n if (options.model) overrides.model = options.model as string;\n if (options.apiKey) overrides.apiKey = options.apiKey as string;\n if (options.baseUrl) overrides.baseUrl = options.baseUrl as string;\n if (typeof options.headless === \"boolean\")\n overrides.headless = options.headless;\n if (options.retries) overrides.maxRetries = options.retries as number;\n if (options.reportDir) overrides.reportDir = options.reportDir as string;\n if (options.report === false) overrides.report = false;\n if (options.viewport) {\n const [w, h] = (options.viewport as string).split(\"x\").map(Number);\n if (w && h) {\n overrides.viewportWidth = w;\n overrides.viewportHeight = h;\n }\n }\n\n const config = loadConfig(overrides);\n const logger = createLogger();\n\n logger.info(\n `Provider: ${c.cyan(config.provider)} | Model: ${c.cyan(config.model)}`,\n );\n logger.info(\n `Viewport: ${config.viewportWidth}x${config.viewportHeight} | Headless: ${config.headless}`,\n );\n logger.info(\n `Max retries: ${config.maxRetries} | Action delay: ${config.actionDelay}ms`,\n );\n\n const result = await runTestFile(file, config, logger);\n process.exit(result.totalFailed > 0 ? 1 : 0);\n });\n\n program\n .command(\"init\")\n .description(\"Initialize a new Vouch project with example files\")\n .action(async () => {\n await loadLoggerDeps();\n await initProject(false);\n });\n\n return program;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { TestRunResult, TestSuite, VouchConfig } from \"../types/index\";\nimport { DEFAULT_CONFIG } from \"../types/index\";\nimport { VisionQAEngine } from \"../engine/vision.js\";\nimport { BrowserController } from \"../browser/controller.js\";\nimport { ActionCoordinator } from \"../actions/coordinator.js\";\nimport { parseVchFile } from \"../parser/vch-parser.js\";\nimport { generateJSONReport } from \"../reporter/json-reporter.js\";\n\n/**\n * Loads and merges configuration from:\n * 1. Default config\n * 2. vouch.config.json (if present)\n * 3. Environment variables\n * 4. CLI overrides\n */\nexport function loadConfig(overrides: Partial<VouchConfig> = {}): VouchConfig {\n let fileConfig: Partial<VouchConfig> = {};\n\n // Try to load vouch.config.json from CWD\n const configPath = path.resolve(\"vouch.config.json\");\n if (fs.existsSync(configPath)) {\n try {\n const raw = fs.readFileSync(configPath, \"utf-8\");\n fileConfig = JSON.parse(raw);\n } catch {\n // Ignore malformed config\n }\n }\n\n // Environment variable mappings\n const envConfig: Partial<VouchConfig> = {};\n if (process.env.VOUCH_PROVIDER)\n envConfig.provider = process.env.VOUCH_PROVIDER as VouchConfig[\"provider\"];\n if (process.env.VOUCH_MODEL) envConfig.model = process.env.VOUCH_MODEL;\n if (process.env.VOUCH_API_KEY) envConfig.apiKey = process.env.VOUCH_API_KEY;\n if (process.env.VOUCH_BASE_URL)\n envConfig.baseUrl = process.env.VOUCH_BASE_URL;\n if (process.env.VOUCH_HEADLESS)\n envConfig.headless = process.env.VOUCH_HEADLESS === \"true\";\n\n return {\n ...DEFAULT_CONFIG,\n ...fileConfig,\n ...envConfig,\n ...overrides,\n };\n}\n\n/**\n * Core test runner β orchestrates the full test execution lifecycle.\n */\nexport async function runTestFile(\n filePath: string,\n config: VouchConfig,\n logger: Logger,\n): Promise<TestRunResult> {\n // 1. Parse the test file\n const suite = parseVchFile(filePath);\n logger.suiteStart(suite);\n\n // 2. Initialize components\n const engine = new VisionQAEngine(config);\n const browser = new BrowserController(config);\n const coordinator = new ActionCoordinator(engine, browser, config);\n\n const result: TestRunResult = {\n suite,\n results: [],\n startTime: Date.now(),\n endTime: 0,\n totalPassed: 0,\n totalFailed: 0,\n totalSkipped: 0,\n };\n\n try {\n // 3. Launch browser\n logger.info(\"Launching browser...\");\n await browser.launch();\n logger.info(\"Browser ready.\");\n\n // 4. Execute steps sequentially\n const actionSteps = suite.steps.filter((s) => s.type !== \"comment\");\n for (let i = 0; i < suite.steps.length; i++) {\n const step = suite.steps[i];\n const isLastActionStep = step === actionSteps[actionSteps.length - 1];\n logger.stepStart(step);\n\n const stepResult = await coordinator.executeStep(step, isLastActionStep);\n result.results.push(stepResult);\n\n switch (stepResult.status) {\n case \"passed\":\n result.totalPassed++;\n break;\n case \"failed\":\n result.totalFailed++;\n break;\n case \"skipped\":\n result.totalSkipped++;\n break;\n }\n\n logger.stepEnd(stepResult);\n\n // If a critical step fails, we could choose to abort\n // For now, continue executing remaining steps\n }\n } catch (err) {\n logger.error(\n `Fatal error: ${err instanceof Error ? err.message : String(err)}`,\n );\n } finally {\n // 5. Close browser\n await browser.close();\n logger.info(\"Browser closed.\");\n const video = browser.getVideoPath();\n if (video) {\n logger.info(`Video saved: ${path.resolve(video)}`);\n }\n }\n\n result.endTime = Date.now();\n\n // 6. Generate report\n if (config.report) {\n const reportFile = generateJSONReport(result, config.reportDir);\n logger.info(`Report generated: ${path.resolve(reportFile)}`);\n }\n\n logger.suiteEnd(result);\n return result;\n}\n\n// βββ Logger Interface ββββββββββββββββββββββββββββββββββββββββββββββββ\n\nexport interface Logger {\n info(msg: string): void;\n error(msg: string): void;\n suiteStart(suite: TestSuite): void;\n suiteEnd(result: TestRunResult): void;\n stepStart(step: import(\"../types/index\").TestStep): void;\n stepEnd(result: import(\"../types/index\").StepResult): void;\n}\n","// βββ Core Action Types βββββββββββββββββββββββββββββββββββββββββββββββ\n\n/** Action type is a free string β AI can output any action (click, type, scroll, hover, keypress, etc.) */\nexport type ActionType = string;\n\n/** Terminal actions that signal the end of a step. */\nexport const TERMINAL_ACTIONS = [\"complete\", \"fail\"] as const;\n\nexport interface VisionQAResponse {\n reasoning: string;\n action: ActionType;\n x: number;\n y: number;\n textPayload: string;\n detectedValidationError: string;\n}\n\n// βββ History Ledger ββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nexport interface HistoryEntry {\n attempt: number;\n action: ActionType;\n x: number;\n y: number;\n textPayload?: string;\n timestamp: number;\n success: boolean;\n error?: string;\n detectedValidationError?: string;\n}\n\n// βββ Test Step βββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nexport interface TestStep {\n lineNumber: number;\n raw: string;\n instruction: string;\n type: \"navigate\" | \"action\" | \"assert\" | \"wait\" | \"conditional\" | \"comment\";\n meta?: Record<string, string>;\n}\n\nexport type StepResult = {\n step: TestStep;\n status: \"passed\" | \"failed\" | \"skipped\";\n duration: number;\n attempts: HistoryEntry[];\n error?: string;\n};\n\n// βββ Test Suite ββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nexport interface TestSuite {\n name: string;\n filePath: string;\n steps: TestStep[];\n}\n\nexport interface TestRunResult {\n suite: TestSuite;\n results: StepResult[];\n startTime: number;\n endTime: number;\n totalPassed: number;\n totalFailed: number;\n totalSkipped: number;\n}\n\n// βββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nexport type AIProvider = \"openai\" | \"anthropic\" | \"google\" | \"ollama\";\n\nexport interface VouchConfig {\n /** AI provider to use for vision analysis */\n provider: AIProvider;\n /** Model identifier (e.g., \"gpt-4o\", \"claude-sonnet-4-20250514\", \"gemini-2.0-flash\") */\n model: string;\n /** API key for the chosen provider */\n apiKey?: string;\n /** Base URL override (for Ollama or proxies) */\n baseUrl?: string;\n /** Browser viewport width */\n viewportWidth: number;\n /** Browser viewport height */\n viewportHeight: number;\n /** Run browser in headless mode */\n headless: boolean;\n /** Maximum retry attempts per step (Actor-Critic loop) */\n maxRetries: number;\n /** Delay between actions in ms */\n actionDelay: number;\n /** Timeout per step in ms */\n stepTimeout: number;\n /** Generate JSON report */\n report: boolean;\n /** Report output directory */\n reportDir: string;\n /** Record video of the test session */\n recordVideo: boolean;\n /** Video output directory */\n videoDir: string;\n}\n\nexport const DEFAULT_CONFIG: VouchConfig = {\n provider: \"openai\",\n model: \"gpt-4o\",\n viewportWidth: 1280,\n viewportHeight: 800,\n headless: false,\n maxRetries: 3,\n actionDelay: 200,\n stepTimeout: 30000,\n report: true,\n reportDir: \"./.vouch/reports\",\n recordVideo: false,\n videoDir: \"./.vouch/videos\",\n};\n\n// βββ AI Provider Interface ββββββββββββββββββββββββββββββββββββββββββ\n\nexport interface AIProviderClient {\n analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[]\n ): Promise<VisionQAResponse>;\n}\n\n// βββ Browser Controller Interface βββββββββββββββββββββββββββββββββββ\n\nexport interface BrowserActions {\n launch(): Promise<void>;\n close(): Promise<void>;\n navigate(url: string): Promise<void>;\n click(pixelX: number, pixelY: number): Promise<void>;\n type(pixelX: number, pixelY: number, text: string): Promise<void>;\n wait(ms: number): Promise<void>;\n getViewportSize(): { width: number; height: number };\n /** Uses Chrome Accessibility API (screen reader) to read the page β zero selectors. */\n getScreenReaderOutput(): Promise<string>;\n getVideoPath(): string | null;\n}\n","/**\n * VisionQA-Engine System Prompt\n * Token-optimized for faster response and lower cost.\n */\n\nexport const VISION_QA_SYSTEM_PROMPT = `QA engine. Input: instruction, UI tree, history. Output: JSON only.\nGrid: 0-1000. Format: role \"name\" v=\"val\" @x,y\nRules: fix typos, self-heal from history, detect validation errors, use 'complete' when done, 'fail' if stuck.\n{\"reasoning\":\"...\",\"action\":\"click|type|wait|scroll|hover|keypress|select|upload|complete|fail\",\"x\":0,\"y\":0,\"textPayload\":\"\",\"detectedValidationError\":\"\"}`;\n\n\n\nexport function buildUserMessage(\n stepInstruction: string,\n historyLedger: Array<{\n attempt: number;\n action: string;\n x: number;\n y: number;\n textPayload?: string;\n success: boolean;\n error?: string;\n detectedValidationError?: string;\n }>,\n screenReaderOutput: string\n): string {\n const parts: string[] = [];\n\n parts.push(`INSTRUCTION: ${stepInstruction}`);\n\n if (screenReaderOutput) {\n parts.push(`\\n${screenReaderOutput}`);\n }\n\n if (historyLedger.length > 0) {\n parts.push(`\\nHISTORY:`);\n for (const e of historyLedger) {\n parts.push(\n `#${e.attempt}: ${e.action}@${e.x},${e.y}${e.textPayload ? ` t=${e.textPayload}` : \"\"} -> ${e.success ? \"OK\" : \"FAIL\"}${e.error ? ` err=${e.error}` : \"\"}${e.detectedValidationError ? ` val=${e.detectedValidationError}` : \"\"}`\n );\n }\n }\n\n parts.push(\"\\nRespond with JSON.\");\n\n return parts.join(\"\\n\");\n}\n","import OpenAI from \"openai\";\nimport type { HistoryEntry, VisionQAResponse } from \"../../types/index.js\";\nimport { buildUserMessage } from \"../prompts.js\";\nimport { BaseProvider } from \"./base.js\";\n\n/**\n * OpenAI provider with streaming + early JSON cutoff.\n * Aborts the stream the moment a complete JSON response is detected.\n */\nexport class OpenAIProvider extends BaseProvider {\n private client: OpenAI;\n private model: string;\n\n constructor(apiKey: string, model: string = \"gpt-4o\", baseUrl?: string) {\n super();\n this.client = new OpenAI({\n apiKey,\n ...(baseUrl ? { baseURL: baseUrl } : {}),\n });\n this.model = model;\n }\n\n async analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[]\n ): Promise<VisionQAResponse> {\n const userMessage = buildUserMessage(stepInstruction, historyLedger, screenReaderOutput);\n\n const stream = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: 300,\n temperature: 0.1,\n stream: true,\n messages: [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userMessage },\n ],\n });\n\n let accumulated = \"\";\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta?.content ?? \"\";\n accumulated += delta;\n\n // Early cutoff β stop as soon as we have complete JSON\n if (this.hasCompleteJSON(accumulated)) {\n stream.controller.abort();\n break;\n }\n }\n\n if (!accumulated.trim()) throw new Error(\"OpenAI returned empty response\");\n return this.parseResponse(accumulated);\n }\n}\n\n","import type {\n AIProviderClient,\n HistoryEntry,\n VisionQAResponse,\n} from \"../../types/index\";\n\n/**\n * Base helper for all AI providers.\n * Handles response parsing, validation, and streaming JSON detection.\n */\nexport abstract class BaseProvider implements AIProviderClient {\n abstract analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[],\n ): Promise<VisionQAResponse>;\n\n /**\n * Parses the raw AI response text into a validated VisionQAResponse.\n */\n protected parseResponse(raw: string): VisionQAResponse {\n let cleaned = raw.trim();\n if (cleaned.startsWith(\"```\")) {\n cleaned = cleaned\n .replace(/^```(?:json)?\\s*\\n?/, \"\")\n .replace(/\\n?```\\s*$/, \"\");\n }\n cleaned = cleaned.replace(/,\\s*([\\]}])/g, \"$1\");\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(cleaned);\n } catch {\n const match = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n const extracted = match[0].replace(/,\\s*([\\]}])/g, \"$1\");\n parsed = JSON.parse(extracted);\n } else {\n throw new Error(\n `Failed to parse AI response as JSON:\\n${raw.slice(0, 500)}`,\n );\n }\n }\n\n return {\n reasoning: String(parsed.reasoning ?? \"\"),\n action: String(parsed.action ?? \"fail\"),\n x: Math.round(Number(parsed.x ?? 500)),\n y: Math.round(Number(parsed.y ?? 500)),\n textPayload: String(parsed.textPayload ?? \"\"),\n detectedValidationError: String(parsed.detectedValidationError ?? \"\"),\n };\n }\n\n /**\n * Checks if accumulated text contains a complete JSON object.\n * Returns true when we can safely stop streaming.\n * Uses brace-depth tracking with proper string/escape handling.\n */\n protected hasCompleteJSON(text: string): boolean {\n let depth = 0;\n let inString = false;\n let escape = false;\n let started = false;\n\n for (const ch of text) {\n if (escape) { escape = false; continue; }\n if (ch === \"\\\\\" && inString) { escape = true; continue; }\n if (ch === '\"') { inString = !inString; continue; }\n if (inString) continue;\n if (ch === \"{\") { depth++; started = true; }\n else if (ch === \"}\") {\n depth--;\n if (started && depth === 0) return true;\n }\n }\n return false;\n }\n}\n\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport type { HistoryEntry, VisionQAResponse } from \"../../types/index\";\nimport { buildUserMessage } from \"../prompts.js\";\nimport { BaseProvider } from \"./base.js\";\n\n/**\n * Anthropic provider with streaming + early JSON cutoff.\n * Aborts the stream the moment a complete JSON response is detected.\n */\nexport class AnthropicProvider extends BaseProvider {\n private client: Anthropic;\n private model: string;\n\n constructor(\n apiKey: string,\n model: string = \"claude-sonnet-4-20250514\",\n baseUrl?: string,\n ) {\n super();\n this.client = new Anthropic({\n apiKey,\n ...(baseUrl ? { baseURL: baseUrl } : {}),\n });\n this.model = model;\n }\n\n async analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[],\n ): Promise<VisionQAResponse> {\n const userMessage = buildUserMessage(\n stepInstruction,\n historyLedger,\n screenReaderOutput,\n );\n\n const stream = this.client.messages.stream({\n model: this.model,\n max_tokens: 300,\n temperature: 0.1,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n });\n\n let accumulated = \"\";\n\n for await (const event of stream) {\n if (\n event.type === \"content_block_delta\" &&\n event.delta.type === \"text_delta\"\n ) {\n accumulated += event.delta.text;\n\n // Early cutoff β stop as soon as we have complete JSON\n if (this.hasCompleteJSON(accumulated)) {\n stream.abort();\n break;\n }\n }\n }\n\n if (!accumulated.trim()) {\n throw new Error(\"Anthropic returned no text content\");\n }\n return this.parseResponse(accumulated);\n }\n}\n\n","import { GoogleGenerativeAI } from \"@google/generative-ai\";\nimport type { HistoryEntry, VisionQAResponse } from \"../../types/index.js\";\nimport { buildUserMessage } from \"../prompts.js\";\nimport { BaseProvider } from \"./base.js\";\n\n/**\n * Google Gemini provider with streaming + early JSON cutoff.\n * Breaks the stream loop the moment a complete JSON response is detected.\n */\nexport class GoogleProvider extends BaseProvider {\n private genAI: GoogleGenerativeAI;\n private model: string;\n\n constructor(apiKey: string, model: string = \"gemini-2.0-flash\") {\n super();\n this.genAI = new GoogleGenerativeAI(apiKey);\n this.model = model;\n }\n\n async analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[],\n ): Promise<VisionQAResponse> {\n const userMessage = buildUserMessage(\n stepInstruction,\n historyLedger,\n screenReaderOutput,\n );\n const generativeModel = this.genAI.getGenerativeModel({\n model: this.model,\n systemInstruction: systemPrompt,\n });\n\n const result = await generativeModel.generateContentStream([\n { text: userMessage },\n ]);\n\n let accumulated = \"\";\n for await (const chunk of result.stream) {\n const text = chunk.text();\n if (text) {\n accumulated += text;\n\n // Early cutoff β stop as soon as we have complete JSON\n if (this.hasCompleteJSON(accumulated)) {\n break;\n }\n }\n }\n\n if (!accumulated.trim()) throw new Error(\"Google Gemini returned empty response\");\n return this.parseResponse(accumulated);\n }\n}\n\n","import type { HistoryEntry, VisionQAResponse } from \"../../types/index.js\";\nimport { buildUserMessage } from \"../prompts.js\";\nimport { BaseProvider } from \"./base.js\";\n\n/**\n * Ollama provider for running local AI models.\n * Uses streaming with early JSON cutoff β aborts inference\n * the moment a complete JSON object is detected.\n */\nexport class OllamaProvider extends BaseProvider {\n private baseUrl: string;\n private model: string;\n\n constructor(\n model: string = \"llava\",\n baseUrl: string = \"http://localhost:11434\",\n ) {\n super();\n this.model = model;\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n }\n\n async analyze(\n systemPrompt: string,\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[],\n ): Promise<VisionQAResponse> {\n const userMessage = buildUserMessage(\n stepInstruction,\n historyLedger,\n screenReaderOutput,\n );\n\n const controller = new AbortController();\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n signal: controller.signal,\n body: JSON.stringify({\n model: this.model,\n stream: true,\n keep_alive: \"5m\",\n options: {\n temperature: 0.1,\n num_predict: 300,\n },\n messages: [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userMessage },\n ],\n }),\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`Ollama request failed (${response.status}): ${errText}`);\n }\n\n // Stream tokens and abort the instant we have valid JSON\n const raw = await this.streamUntilJSON(response, controller);\n if (!raw) throw new Error(\"Ollama returned empty response\");\n return this.parseResponse(raw);\n }\n\n /**\n * Reads the streaming response token-by-token.\n * As soon as the accumulated text contains a complete JSON object\n * (balanced braces), we abort the connection and return immediately.\n * This saves significant inference time on trailing tokens.\n */\n private async streamUntilJSON(\n response: Response,\n controller: AbortController,\n ): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"No response body from Ollama\");\n\n const decoder = new TextDecoder();\n let accumulated = \"\";\n let braceDepth = 0;\n let inString = false;\n let escape = false;\n let jsonStarted = false;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value, { stream: true });\n // Each line is a JSON object like: {\"message\":{\"content\":\"...\"}, \"done\":false}\n const lines = chunk.split(\"\\n\").filter((l) => l.trim());\n\n for (const line of lines) {\n try {\n const data = JSON.parse(line) as {\n message?: { content?: string };\n done?: boolean;\n };\n const token = data.message?.content ?? \"\";\n accumulated += token;\n\n // Track brace depth for early cutoff\n for (const ch of token) {\n if (escape) {\n escape = false;\n continue;\n }\n if (ch === \"\\\\\" && inString) {\n escape = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n if (ch === \"{\") {\n braceDepth++;\n jsonStarted = true;\n } else if (ch === \"}\") {\n braceDepth--;\n // Complete JSON object detected β abort immediately\n if (jsonStarted && braceDepth === 0) {\n controller.abort();\n return accumulated;\n }\n }\n }\n\n if (data.done) {\n return accumulated;\n }\n } catch {\n // Malformed line, skip\n }\n }\n }\n } catch (err: any) {\n // AbortError is expected β we intentionally abort on complete JSON\n if (err?.name === \"AbortError\") {\n return accumulated;\n }\n // If we already have content, return it despite the error\n if (accumulated.trim()) return accumulated;\n throw err;\n } finally {\n reader.releaseLock();\n }\n\n return accumulated;\n }\n}\n\n","import type {\n AIProvider,\n AIProviderClient,\n HistoryEntry,\n VisionQAResponse,\n VouchConfig,\n} from \"../types/index\";\nimport { TERMINAL_ACTIONS } from \"../types/index\";\nimport { VISION_QA_SYSTEM_PROMPT } from \"./prompts.js\";\nimport { OpenAIProvider } from \"./providers/openai.js\";\nimport { AnthropicProvider } from \"./providers/anthropic.js\";\nimport { GoogleProvider } from \"./providers/google.js\";\nimport { OllamaProvider } from \"./providers/ollama.js\";\n\n/**\n * Creates the appropriate AI provider client based on config.\n */\nexport function createProvider(config: VouchConfig): AIProviderClient {\n const providers: Record<AIProvider, () => AIProviderClient> = {\n openai: () => {\n if (!config.apiKey)\n throw new Error(\n \"OpenAI API key is required. Set VOUCH_API_KEY or provider.apiKey in config.\",\n );\n return new OpenAIProvider(config.apiKey, config.model, config.baseUrl);\n },\n anthropic: () => {\n if (!config.apiKey)\n throw new Error(\n \"Anthropic API key is required. Set VOUCH_API_KEY or provider.apiKey in config.\",\n );\n return new AnthropicProvider(config.apiKey, config.model, config.baseUrl);\n },\n google: () => {\n if (!config.apiKey)\n throw new Error(\n \"Google API key is required. Set VOUCH_API_KEY or provider.apiKey in config.\",\n );\n return new GoogleProvider(config.apiKey, config.model);\n },\n ollama: () => {\n return new OllamaProvider(\n config.model,\n config.baseUrl || \"http://localhost:11434\",\n );\n },\n };\n\n const factory = providers[config.provider];\n if (!factory) {\n throw new Error(\n `Unknown AI provider: \"${config.provider}\". Supported: openai, anthropic, google, ollama`,\n );\n }\n return factory();\n}\n\n/**\n * VisionQA Engine β the cognitive core of Vouch.\n *\n * Uses screen reader output (not screenshots) for AI analysis.\n */\nexport class VisionQAEngine {\n private provider: AIProviderClient;\n private config: VouchConfig;\n\n constructor(config: VouchConfig) {\n this.config = config;\n this.provider = createProvider(config);\n }\n\n /**\n * Analyze the page using screen reader output and step instruction.\n */\n async analyze(\n stepInstruction: string,\n screenReaderOutput: string,\n historyLedger: HistoryEntry[],\n ): Promise<VisionQAResponse> {\n return this.provider.analyze(\n VISION_QA_SYSTEM_PROMPT,\n stepInstruction,\n screenReaderOutput,\n historyLedger,\n );\n }\n\n /**\n * Convert normalized coordinates (0-1000) to pixel coordinates.\n */\n toPixelCoords(\n normalizedX: number,\n normalizedY: number,\n viewportWidth: number,\n viewportHeight: number,\n ): { pixelX: number; pixelY: number } {\n return {\n pixelX: Math.round((normalizedX / 1000) * viewportWidth),\n pixelY: Math.round((normalizedY / 1000) * viewportHeight),\n };\n }\n\n /**\n * Checks if the response indicates a terminal action.\n */\n isTerminal(response: VisionQAResponse): boolean {\n return (TERMINAL_ACTIONS as readonly string[]).includes(response.action);\n }\n\n /**\n * Checks if the response contains a validation error.\n */\n hasValidationError(response: VisionQAResponse): boolean {\n return response.detectedValidationError.length > 0;\n }\n}\n","import puppeteer, { type Browser, type Page, type CDPSession } from \"puppeteer\";\nimport { PuppeteerScreenRecorder } from \"puppeteer-screen-recorder\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { BrowserActions, VouchConfig } from \"../types/index.js\";\n\n/** Hoisted for performance β avoid recreating per call */\nconst SKIP_ROLES = new Set([\n \"none\", \"generic\", \"InlineTextBox\", \"LineBreak\",\n \"paragraph\", \"Section\", \"group\", \"document\",\n \"WebArea\", \"main\", \"navigation\", \"banner\",\n \"contentinfo\", \"complementary\", \"list\", \"listitem\",\n \"StaticText\", \"rootWebArea\",\n]);\n\n/**\n * Puppeteer-based browser controller.\n *\n * Uses Chrome DevTools Protocol Accessibility API (screen reader)\n * to understand the page β zero CSS/XPath selectors.\n *\n * Optimized for low-latency execution:\n * - Persistent CDP session (no create/detach per call)\n * - Batched box model resolution\n * - Minimal sleep timers\n * - Instant keyboard typing\n */\nexport class BrowserController implements BrowserActions {\n private browser: Browser | null = null;\n private page: Page | null = null;\n private cdpClient: CDPSession | null = null;\n private config: VouchConfig;\n private recorder: PuppeteerScreenRecorder | null = null;\n public videoPath: string | null = null;\n\n constructor(config: VouchConfig) {\n this.config = config;\n }\n\n async launch(): Promise<void> {\n this.browser = await puppeteer.launch({\n headless: this.config.headless,\n defaultViewport: {\n width: this.config.viewportWidth,\n height: this.config.viewportHeight,\n },\n args: [\n \"--no-sandbox\",\n \"--disable-setuid-sandbox\",\n \"--disable-dev-shm-usage\",\n \"--disable-extensions\",\n \"--disable-background-networking\",\n \"--disable-sync\",\n \"--disable-translate\",\n \"--metrics-recording-only\",\n \"--no-first-run\",\n `--window-size=${this.config.viewportWidth},${this.config.viewportHeight}`,\n ],\n });\n\n const pages = await this.browser.pages();\n this.page = pages[0] || (await this.browser.newPage());\n\n // Set a reasonable navigation timeout\n this.page.setDefaultNavigationTimeout(this.config.stepTimeout);\n this.page.setDefaultTimeout(this.config.stepTimeout);\n\n // Create a persistent CDP session β reused for all accessibility reads\n this.cdpClient = await this.page.createCDPSession();\n\n if (this.config.recordVideo) {\n if (!fs.existsSync(this.config.videoDir)) {\n fs.mkdirSync(this.config.videoDir, { recursive: true });\n }\n this.videoPath = path.join(\n this.config.videoDir,\n `vouch-recording-${Date.now()}.mp4`\n );\n this.recorder = new PuppeteerScreenRecorder(this.page);\n await this.recorder.start(this.videoPath);\n }\n }\n\n async close(): Promise<void> {\n if (this.recorder) {\n try {\n await this.recorder.stop();\n } catch {}\n this.recorder = null;\n }\n\n if (this.cdpClient) {\n try {\n await this.cdpClient.detach();\n } catch {}\n this.cdpClient = null;\n }\n\n if (this.browser) {\n try {\n await this.browser.close();\n } catch {\n // Ignore errors during close\n }\n this.browser = null;\n this.page = null;\n }\n }\n\n getVideoPath(): string | null {\n return this.videoPath;\n }\n\n async navigate(url: string): Promise<void> {\n this.assertPage();\n // domcontentloaded is significantly faster than networkidle2\n await this.page!.goto(url, { waitUntil: \"domcontentloaded\" });\n // Brief settle for JS-rendered content\n await this.sleep(150);\n }\n\n async click(pixelX: number, pixelY: number): Promise<void> {\n this.assertPage();\n await this.page!.mouse.click(pixelX, pixelY);\n await this.sleep(50);\n }\n\n async type(pixelX: number, pixelY: number, text: string): Promise<void> {\n this.assertPage();\n // Triple-click to select all, then overwrite\n await this.page!.mouse.click(pixelX, pixelY, { count: 3 });\n await this.sleep(50);\n await this.page!.keyboard.press(\"Backspace\");\n // Instant typing β no per-character delay\n await this.page!.keyboard.type(text, { delay: 0 });\n await this.sleep(50);\n }\n\n async wait(ms: number): Promise<void> {\n await this.sleep(ms);\n }\n\n getViewportSize(): { width: number; height: number } {\n return {\n width: this.config.viewportWidth,\n height: this.config.viewportHeight,\n };\n }\n\n getPage(): Page {\n this.assertPage();\n return this.page!;\n }\n\n /**\n * Uses Chrome DevTools Protocol Accessibility API to read the page\n * like a screen reader. Returns a textual description of all visible\n * interactive elements with their normalized (0-1000) coordinates.\n *\n * Performance: reuses persistent CDP session and batches box model lookups.\n */\n async getScreenReaderOutput(): Promise<string> {\n this.assertPage();\n const client = this.cdpClient!;\n\n const { nodes } = await client.send(\"Accessibility.getFullAXTree\") as {\n nodes: Array<{\n role?: { value: string };\n name?: { value: string };\n value?: { value: string };\n backendDOMNodeId?: number;\n }>;\n };\n\n const { width: vw, height: vh } = this.getViewportSize();\n\n // Roles we skip β structural containers, not interactive\n const skipRoles = SKIP_ROLES;\n\n // Phase 1: Filter relevant nodes (CPU-only, instant)\n type RelevantNode = {\n role: string;\n name: string;\n value?: string;\n backendNodeId: number;\n };\n const relevant: RelevantNode[] = [];\n for (const node of nodes) {\n const role = node.role?.value;\n const name = node.name?.value?.trim();\n if (!role || skipRoles.has(role) || !name || name === \"\") continue;\n const backendNodeId = node.backendDOMNodeId;\n if (!backendNodeId) continue;\n relevant.push({ role, name, value: node.value?.value, backendNodeId });\n }\n\n // Phase 2: Batch resolve box models in parallel\n const boxPromises = relevant.map((n) =>\n client\n .send(\"DOM.getBoxModel\", { backendNodeId: n.backendNodeId })\n .then((r) => (r as { model: { content: number[] } }).model)\n .catch(() => null)\n );\n const boxes = await Promise.allSettled(boxPromises);\n\n // Phase 3: Build compressed output β terse format saves ~40% tokens\n const results: string[] = [];\n for (let i = 0; i < relevant.length; i++) {\n const settled = boxes[i];\n if (settled.status !== \"fulfilled\" || !settled.value) continue;\n const model = settled.value;\n const quad = model.content;\n const centerX = (quad[0] + quad[4]) / 2;\n const centerY = (quad[1] + quad[5]) / 2;\n if (centerX < 0 || centerY < 0 || centerX > vw || centerY > vh) continue;\n\n const normX = Math.round((centerX / vw) * 1000);\n const normY = Math.round((centerY / vh) * 1000);\n const n = relevant[i];\n // Terse format: role \"name\" [val] @x,y\n let desc = `${n.role} \"${n.name}\"`;\n if (n.value) desc += ` v=\"${n.value}\"`;\n desc += ` @${normX},${normY}`;\n results.push(desc);\n }\n\n return results.length > 0\n ? \"UI:\\n\" + results.join(\"\\n\")\n : \"UI: empty\";\n }\n\n private assertPage(): void {\n if (!this.page) {\n throw new Error(\"Browser not launched. Call launch() first.\");\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n HistoryEntry,\n StepResult,\n TestStep,\n VisionQAResponse,\n VouchConfig,\n} from \"../types/index\";\nimport { VisionQAEngine } from \"../engine/vision.js\";\nimport { BrowserController } from \"../browser/controller.js\";\n\n/**\n * Actor-Critic Action Coordinator.\n *\n * Flow per step:\n * 1. Screen reader β AI analysis β execute action β repeat\n * 2. Actor-Critic loop for automatic self-healing\n */\nexport class ActionCoordinator {\n private engine: VisionQAEngine;\n private browser: BrowserController;\n private config: VouchConfig;\n\n constructor(\n engine: VisionQAEngine,\n browser: BrowserController,\n config: VouchConfig,\n ) {\n this.engine = engine;\n this.browser = browser;\n this.config = config;\n }\n\n /**\n * Execute a single test step with self-healing retries.\n */\n async executeStep(step: TestStep, isLastStep: boolean): Promise<StepResult> {\n const startTime = Date.now();\n const history: HistoryEntry[] = [];\n\n // Handle special step types\n if (step.type === \"comment\") {\n return { step, status: \"skipped\", duration: 0, attempts: [] };\n }\n\n if (step.type === \"navigate\") {\n try {\n const url = step.meta?.url ?? step.instruction;\n await this.browser.navigate(url);\n return {\n step,\n status: \"passed\",\n duration: Date.now() - startTime,\n attempts: [],\n };\n } catch (err) {\n return {\n step,\n status: \"failed\",\n duration: Date.now() - startTime,\n attempts: [],\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n if (step.type === \"wait\") {\n const ms = parseInt(step.meta?.duration ?? \"2000\", 10);\n await this.browser.wait(ms);\n return {\n step,\n status: \"passed\",\n duration: Date.now() - startTime,\n attempts: [],\n };\n }\n\n // For action, assert, and conditional steps: Actor-Critic loop\n for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {\n try {\n // 1. Read the page via screen reader (accessibility API)\n const screenReader = await this.browser.getScreenReaderOutput();\n\n // 2. Format instruction based on step type\n const instruction =\n step.type === \"assert\"\n ? `VERIFY: \"${step.instruction}\". If confirmed true/visible, respond \"complete\". If not, respond \"fail\" with reasoning.`\n : step.instruction;\n\n // 3. Send to VisionQA engine\n const response = await this.engine.analyze(\n instruction,\n screenReader,\n history,\n );\n\n // 3. Build history entry\n const entry: HistoryEntry = {\n attempt,\n action: response.action,\n x: response.x,\n y: response.y,\n textPayload: response.textPayload || undefined,\n timestamp: Date.now(),\n success: false,\n detectedValidationError:\n response.detectedValidationError || undefined,\n };\n\n // 4. Terminal: complete\n if (response.action === \"complete\") {\n entry.success = true;\n history.push(entry);\n return {\n step,\n status: \"passed\",\n duration: Date.now() - startTime,\n attempts: history,\n };\n }\n\n // 5. Terminal: fail\n if (response.action === \"fail\") {\n entry.success = false;\n entry.error = response.reasoning;\n history.push(entry);\n\n if (attempt < this.config.maxRetries) {\n await this.browser.wait(this.config.actionDelay);\n continue;\n }\n\n return {\n step,\n status: \"failed\",\n duration: Date.now() - startTime,\n attempts: history,\n error: response.reasoning,\n };\n }\n\n // 6. If this is an assert step, it MUST respond complete or fail.\n if (step.type === \"assert\") {\n entry.success = false;\n entry.error = `Invalid action for assert step: \"${response.action}\". You must respond \"complete\" or \"fail\".`;\n history.push(entry);\n continue;\n }\n\n // 7. Execute the action for non-assert steps\n await this.dispatchAction(response);\n\n // 8. Post-action delay\n await this.browser.wait(this.config.actionDelay);\n\n // 9. Check for validation errors (Critic phase)\n if (response.detectedValidationError) {\n entry.success = false;\n entry.error = `Validation error: ${response.detectedValidationError}`;\n history.push(entry);\n continue; // Retry β AI will self-heal from the history\n }\n\n // 10. Wait action β re-evaluate\n if (response.action === \"wait\") {\n entry.success = true;\n history.push(entry);\n continue;\n }\n\n // 11. Mark success for action step\n entry.success = true;\n history.push(entry);\n\n return {\n step,\n status: \"passed\",\n duration: Date.now() - startTime,\n attempts: history,\n };\n } catch (err) {\n history.push({\n attempt,\n action: \"fail\",\n x: 0,\n y: 0,\n timestamp: Date.now(),\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // All retries exhausted\n return {\n step,\n status: \"failed\",\n duration: Date.now() - startTime,\n attempts: history,\n error: `Step failed after ${this.config.maxRetries} attempts. Last errors: ${history\n .filter((h) => h.error)\n .map((h) => h.error)\n .join(\" | \")}`,\n };\n }\n\n /**\n * Dispatch any action to the browser β extensible, not a fixed set.\n */\n private async dispatchAction(response: VisionQAResponse): Promise<void> {\n const { width, height } = this.browser.getViewportSize();\n const { pixelX, pixelY } = this.engine.toPixelCoords(\n response.x,\n response.y,\n width,\n height,\n );\n const page = this.browser.getPage();\n\n // 1. Resolve the exact DOM element at these coordinates for smarter interaction\n const elementHandle = await page\n .evaluateHandle((x, y) => document.elementFromPoint(x, y), pixelX, pixelY)\n .catch(() => null);\n\n switch (response.action) {\n case \"click\":\n await this.browser.click(pixelX, pixelY);\n break;\n case \"type\":\n await this.browser.type(pixelX, pixelY, response.textPayload);\n break;\n case \"wait\":\n await this.browser.wait(2000);\n break;\n case \"scroll\":\n await page.mouse.wheel({ deltaY: response.y > 500 ? 300 : -300 });\n break;\n case \"hover\":\n await page.mouse.move(pixelX, pixelY);\n break;\n case \"keypress\":\n await page.keyboard.press(response.textPayload as any);\n break;\n case \"select\":\n // Click to open, then click the option\n await this.browser.click(pixelX, pixelY);\n break;\n case \"upload\":\n // File inputs cannot be clicked normally to upload files via code, it opens an OS dialog.\n // We MUST use the ElementHandle to upload the file directly.\n if (elementHandle && response.textPayload) {\n const el =\n elementHandle.asElement() as unknown as import(\"puppeteer\").ElementHandle<HTMLInputElement>;\n if (el) {\n await el.uploadFile(response.textPayload);\n } else {\n // Fallback\n await this.browser.click(pixelX, pixelY);\n }\n }\n break;\n default:\n // For any unknown action, try to click at the coordinates\n await this.browser.click(pixelX, pixelY);\n break;\n }\n\n // Cleanup handle to prevent memory leaks\n if (elementHandle) {\n await elementHandle.dispose().catch(() => {});\n }\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { TestStep, TestSuite } from \"../types/index\";\n\n/**\n * Parses `.vtest` files into structured TestSuite objects.\n *\n * .vtest Format:\n * βββββββββββββ\n * Lines starting with # are comments\n * Lines starting with > are metadata (suite name, config overrides)\n * Lines starting with @ are directives (@navigate, @wait, @assert)\n * All other non-empty lines are plain English action instructions\n *\n * Example:\n * > name: Login Flow Test\n * @navigate https://example.com/login\n * click on the email input field\n * type test@example.com in the email field\n * click the password field\n * type MySecurePass123! in the password field\n * click the Sign In button\n * @assert Dashboard page is visible\n */\nexport function parseVchFile(filePath: string): TestSuite {\n const absolutePath = path.resolve(filePath);\n if (!fs.existsSync(absolutePath)) {\n throw new Error(`Test file not found: ${absolutePath}`);\n }\n\n const content = fs.readFileSync(absolutePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n let suiteName = path.basename(filePath, \".vch\");\n const steps: TestStep[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i];\n const trimmed = raw.trim();\n const lineNumber = i + 1;\n\n // Skip empty lines\n if (!trimmed) continue;\n\n // Comment lines\n if (trimmed.startsWith(\"#\")) {\n steps.push({\n lineNumber,\n raw,\n instruction: trimmed.slice(1).trim(),\n type: \"comment\",\n });\n continue;\n }\n\n // Metadata lines\n if (trimmed.startsWith(\">\")) {\n const metaContent = trimmed.slice(1).trim();\n const colonIndex = metaContent.indexOf(\":\");\n if (colonIndex > 0) {\n const key = metaContent.slice(0, colonIndex).trim().toLowerCase();\n const value = metaContent.slice(colonIndex + 1).trim();\n if (key === \"name\") {\n suiteName = value;\n }\n }\n continue;\n }\n\n // Navigate directive\n if (trimmed.startsWith(\"@navigate\")) {\n const url = trimmed.replace(/^@navigate\\s+/, \"\").trim();\n steps.push({\n lineNumber,\n raw,\n instruction: url,\n type: \"navigate\",\n meta: { url },\n });\n continue;\n }\n\n // Wait directive\n if (trimmed.startsWith(\"@wait\")) {\n const ms = trimmed.replace(/^@wait\\s+/, \"\").trim();\n steps.push({\n lineNumber,\n raw,\n instruction: `Wait for ${ms}ms`,\n type: \"wait\",\n meta: { duration: ms },\n });\n continue;\n }\n\n // Assert directive\n if (trimmed.startsWith(\"@assert\")) {\n const assertion = trimmed.replace(/^@assert\\s+/, \"\").trim();\n steps.push({\n lineNumber,\n raw,\n instruction: assertion,\n type: \"assert\",\n });\n continue;\n }\n\n // Conditional directive\n if (trimmed.startsWith(\"@if\")) {\n const condition = trimmed.replace(/^@if\\s+/, \"\").trim();\n steps.push({\n lineNumber,\n raw,\n instruction: condition,\n type: \"conditional\",\n });\n continue;\n }\n\n // Plain English action instruction\n steps.push({\n lineNumber,\n raw,\n instruction: trimmed,\n type: \"action\",\n });\n }\n\n return {\n name: suiteName,\n filePath: absolutePath,\n steps,\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { TestRunResult } from \"../types/index\";\n\nexport function generateJSONReport(result: TestRunResult, reportDir: string): string {\n if (!fs.existsSync(reportDir)) {\n fs.mkdirSync(reportDir, { recursive: true });\n }\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const filename = `vouch-report-${timestamp}.json`;\n const filePath = path.join(reportDir, filename);\n\n const reportData = JSON.stringify(result, null, 2);\n \n fs.writeFileSync(filePath, reportData, \"utf-8\");\n return filePath;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { DEFAULT_CONFIG } from \"../types/index.js\";\n\nexport async function initProject(quiet = false) {\n let chalk: typeof import(\"chalk\") | undefined = undefined;\n let figures: typeof import(\"figures\") | undefined = undefined;\n let p: typeof import(\"@clack/prompts\") | null = null;\n\n try {\n chalk = await import(\"chalk\");\n figures = await import(\"figures\");\n if (!quiet) {\n p = await import(\"@clack/prompts\");\n }\n } catch (e) {\n // fallback if dependencies missing in strange environments\n }\n\n const c: any = chalk?.default ?? chalk;\n const f: any = figures?.default ?? figures;\n\n const configContent = JSON.stringify(DEFAULT_CONFIG, null, 2);\n\n if (!fs.existsSync(\"vouch.config.json\")) {\n fs.writeFileSync(\"vouch.config.json\", configContent, \"utf-8\");\n if (p) p.log.success(\"Created vouch.config.json\");\n else if (!quiet && c && f)\n console.log(c.green(` ${f.tick} Created vouch.config.json`));\n } else {\n if (!quiet && c && f)\n console.log(\n c.yellow(` ${f.warning} vouch.config.json already exists, skipping.`),\n );\n }\n\n const exampleDir = \"examples\";\n if (!fs.existsSync(exampleDir)) {\n fs.mkdirSync(exampleDir, { recursive: true });\n }\n\n const exampleTest = `> name: Example Login Flow\\n# This is an example Vouch test file\\n\\n@navigate https://example.com/login\\n\\nclick on the email input field\\ntype test@example.com into the email field\\nclick on the password input field\\ntype SecurePassword123! into the password field\\n\\nclick the Sign In button\\n\\n@assert Dashboard heading is visible\\n`;\n const examplePath = path.join(exampleDir, \"demo.vch\");\n if (!fs.existsSync(examplePath)) {\n fs.writeFileSync(examplePath, exampleTest, \"utf-8\");\n if (p) p.log.success(`Created ${examplePath}`);\n else if (!quiet && c && f)\n console.log(c.green(` ${f.tick} Created ${examplePath}`));\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { initProject } from \"./init.js\";\nimport { loadConfig, runTestFile } from \"./runner.js\";\nimport { createLogger } from \"./logger.js\";\nimport type { VouchConfig } from \"../types/index.js\";\n\nfunction findVchFiles(dir: string, fileList: string[] = []): string[] {\n try {\n const files = fs.readdirSync(dir);\n for (const file of files) {\n if (\n file === \"node_modules\" ||\n file === \".git\" ||\n file === \"dist\" ||\n file === \".vouch\" ||\n file.startsWith(\".\")\n )\n continue;\n const fullPath = path.join(dir, file);\n const stat = fs.statSync(fullPath);\n if (stat.isDirectory()) {\n findVchFiles(fullPath, fileList);\n } else if (file.endsWith(\".vch\")) {\n fileList.push(fullPath);\n }\n }\n } catch (e) {\n // Ignore permissions/read errors\n }\n return fileList;\n}\n\nexport async function runInteractiveMenu() {\n const chalk = await import(\"chalk\");\n const p = await import(\"@clack/prompts\");\n const c = chalk.default ?? chalk;\n\n console.clear();\n console.log(\n c.bold.white(`\n ββ ββ βββββββ ββ ββ βββββββ ββ ββ\nβ β β β β β β β β β β β\nβ β β β β β β β β β βββ β\nβ βββ β β β β βββ β βββ β\nβ β βββ β β β β β β\nβ βββ β β ββββ β β β\n βββββ ββββββββββββββββββββββββββββ ββββ\n `),\n );\n console.log(c.dim(\" The future of visual web automation\\n\"));\n\n p.intro(c.bgWhite(c.black(\" Welcome to Vouch \")));\n\n // Make sure config exists automatically\n if (!fs.existsSync(\"vouch.config.json\")) {\n await initProject(true);\n }\n\n const testFiles = findVchFiles(process.cwd());\n const relativeFiles = testFiles.map((f) => path.relative(process.cwd(), f));\n\n const action = await p.select({\n message: \"What would you like to do?\",\n options: [\n {\n label: \"βΆ Run specific tests\",\n value: \"run_selected\",\n hint: \"Choose files to run\",\n },\n {\n label: \"βΆ Run all tests\",\n value: \"run_all\",\n hint: \"Execute all .vch files\",\n },\n {\n label: \"π Initialize examples\",\n value: \"init\",\n hint: \"Create example tests\",\n },\n { label: \"β Exit\", value: \"exit\" },\n ],\n });\n\n if (action === \"exit\" || p.isCancel(action)) {\n p.outro(\"Goodbye!\");\n process.exit(0);\n }\n\n if (action === \"init\") {\n await initProject(false);\n p.outro(\"Examples initialized! Run 'vouch' again to execute tests.\");\n process.exit(0);\n }\n\n let selectedFiles: string[] = [];\n\n if (action === \"run_all\") {\n if (relativeFiles.length === 0) {\n p.log.warn(\"No .vch files found in the current directory.\");\n process.exit(0);\n }\n selectedFiles = relativeFiles;\n } else if (action === \"run_selected\") {\n if (relativeFiles.length === 0) {\n p.log.warn(\"No .vch files found in the current directory.\");\n process.exit(0);\n }\n\n // Type to search / filter is built-in to @clack/prompts multiselect\n const files = await p.multiselect({\n message: \"Select test files (type to search)\",\n options: relativeFiles.map((f) => ({\n label: path.basename(f), // Just show file name\n value: f,\n hint: path.dirname(f) === \".\" ? undefined : path.dirname(f),\n })),\n required: true,\n });\n\n if (p.isCancel(files)) process.exit(0);\n selectedFiles = files as string[];\n }\n\n const config = loadConfig();\n\n p.outro(\"Starting tests...\");\n\n const logger = createLogger();\n let totalFailed = 0;\n for (const file of selectedFiles) {\n const result = await runTestFile(file, config, logger);\n totalFailed += result.totalFailed;\n }\n\n process.exit(totalFailed > 0 ? 1 : 0);\n}\n","import type {\n StepResult,\n TestRunResult,\n TestStep,\n TestSuite,\n} from \"../types/index.js\";\nimport type { Logger } from \"./runner.js\";\n\nlet chalk: any;\nlet figures: any;\nlet ora: any;\n\nexport async function loadLoggerDeps() {\n chalk = await import(\"chalk\");\n figures = await import(\"figures\");\n ora = await import(\"ora\");\n}\n\nexport function createLogger(): Logger {\n const c: any = chalk?.default ?? chalk;\n const f: any = figures?.default ?? figures;\n const o: any = ora?.default ?? ora;\n\n let currentSpinner: any = null;\n\n return {\n info(msg: string) {\n if (currentSpinner) {\n currentSpinner.info(c.dim(msg));\n currentSpinner.start();\n return;\n }\n if (c && f) console.log(c.dim(` ${f.pointerSmall} ${msg}`));\n else console.log(` > ${msg}`);\n },\n error(msg: string) {\n if (currentSpinner) {\n currentSpinner.fail(c.red(msg));\n currentSpinner = null;\n return;\n }\n if (c && f) console.log(c.red(` ${f.cross} ${msg} `));\n else console.error(` X ${msg}`);\n },\n suiteStart(suite: TestSuite) {\n console.log();\n if (c) {\n console.log(c.bold.green(` πΈοΈ ${suite.name}`));\n console.log(c.dim(` ${suite.filePath}`));\n console.log(\n c.dim(\n ` ${suite.steps.filter((s: any) => s.type !== \"comment\").length} steps`,\n ),\n );\n } else {\n console.log(` πΈοΈ ${suite.name}\\n ${suite.filePath}`);\n }\n console.log();\n },\n suiteEnd(result: TestRunResult) {\n const duration = ((result.endTime - result.startTime) / 1000).toFixed(2);\n console.log();\n if (c && f) {\n console.log(c.dim(\" βββββββββββββββββββββββββββββββββββββ\"));\n if (result.totalFailed === 0) {\n console.log(\n c.bold.green(` ${f.tick} All ${result.totalPassed} steps passed`) +\n c.dim(` (${duration}s)`),\n );\n } else {\n console.log(\n c.bold.green(` ${f.cross} ${result.totalFailed} failed`) +\n c.dim(` | `) +\n c.green(`${result.totalPassed} passed`) +\n c.dim(` (${duration}s)`),\n );\n }\n } else {\n console.log(\n ` ${result.totalFailed === 0 ? \"Passed\" : \"Failed\"}: ${result.totalPassed} passed, ${result.totalFailed} failed (${duration}s)`,\n );\n }\n console.log();\n },\n stepStart(step: TestStep) {\n if (step.type === \"comment\") return;\n if (c && o) {\n const prefix =\n step.type === \"navigate\"\n ? \"π\"\n : step.type === \"assert\"\n ? \"π\"\n : step.type === \"wait\"\n ? \"β³\"\n : \"π―\";\n currentSpinner = o({\n text:\n c.dim(`${prefix} L${step.lineNumber}: `) +\n c.green(step.instruction),\n color: \"green\",\n spinner: \"dots\",\n }).start();\n } else {\n process.stdout.write(` L${step.lineNumber}: ${step.instruction} `);\n }\n },\n stepEnd(result: StepResult) {\n if (result.step.type === \"comment\") return;\n const durationStr = `${(result.duration / 1000).toFixed(1)}s`;\n\n if (currentSpinner) {\n if (result.status === \"passed\") {\n currentSpinner.succeed(\n currentSpinner.text + c.dim(` ${durationStr}`),\n );\n } else if (result.status === \"failed\") {\n currentSpinner.fail(currentSpinner.text + c.dim(` ${durationStr}`));\n if (result.error)\n console.log(c.dim(` ββ `) + c.red(result.error.slice(0, 120)));\n } else {\n currentSpinner.stopAndPersist({\n symbol: c.dim(f.arrowRight),\n text: currentSpinner.text + c.dim(\" skipped\"),\n });\n }\n currentSpinner = null;\n } else {\n if (result.status === \"passed\") {\n if (c && f) console.log(c.green(f.tick) + c.dim(` ${durationStr}`));\n else console.log(` OK ${durationStr}`);\n } else if (result.status === \"failed\") {\n if (c && f) {\n console.log(c.red(f.cross) + c.dim(` ${durationStr}`));\n if (result.error)\n console.log(c.green(` ββ ${result.error.slice(0, 120)}`));\n } else {\n console.log(` FAIL ${durationStr}`);\n if (result.error)\n console.log(` ββ ${result.error.slice(0, 120)}`);\n }\n } else {\n if (c && f) console.log(c.dim(f.arrowRight + \" skipped\"));\n else console.log(\" skipped\");\n }\n }\n },\n };\n}\n","import { createCLI } from \"./cli/commands.js\";\n\n/**\n * β‘ Vouch β Zero-selector, vision-driven web and desktop automation.\n *\n * Entry point for the CLI binary.\n */\nasync function main() {\n const program = createCLI();\n await program.parseAsync(process.argv);\n}\n\nmain().catch((err) => {\n console.error(\"Fatal:\", err);\n process.exit(1);\n});\n"],"mappings":"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","names":["fs","fs","path","resolve","fs","path","fs","path","fs","path","chalk","figures","fs","path","chalk","chalk"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
> name: Example Login Flow
|
|
2
|
+
# This is an example Vouch test file
|
|
3
|
+
# Vouch uses computer vision to interact with web apps β no selectors needed!
|
|
4
|
+
|
|
5
|
+
@navigate https://example.com/login
|
|
6
|
+
|
|
7
|
+
# Fill in the login form
|
|
8
|
+
click on the email input field
|
|
9
|
+
type test@example.com into the email field
|
|
10
|
+
click on the password input field
|
|
11
|
+
type SecurePassword123! into the password field
|
|
12
|
+
|
|
13
|
+
# Submit the form
|
|
14
|
+
click the Sign In button
|
|
15
|
+
|
|
16
|
+
# Verify the dashboard loaded
|
|
17
|
+
@assert Dashboard heading is visible
|
|
18
|
+
@assert Navigation sidebar is present
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
> name: Real World Internet Login Flow
|
|
2
|
+
# Testing the Vouch MVP using a public test site and local Ollama model
|
|
3
|
+
|
|
4
|
+
@navigate https://the-internet.herokuapp.com/login
|
|
5
|
+
|
|
6
|
+
# Enter username
|
|
7
|
+
click on the username input field
|
|
8
|
+
type tomsmith into the username field
|
|
9
|
+
|
|
10
|
+
# Enter password
|
|
11
|
+
click on the password input field
|
|
12
|
+
type SuperSecretPassword! into the password field
|
|
13
|
+
|
|
14
|
+
# Submit the form
|
|
15
|
+
click on the login button
|
|
16
|
+
|
|
17
|
+
# Wait for page to load after login
|
|
18
|
+
@wait 2000
|
|
19
|
+
|
|
20
|
+
# Verify the secure area page loaded
|
|
21
|
+
@assert Secure Area page is visible with logout button
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inamul_hasan/vouch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-selector, vision-driven web and desktop automation engine. Acts like a human QA engineer using AI-powered computer vision.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"vouch": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "bunx tsup",
|
|
13
|
+
"dev": "bunx tsup --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"lint": "bunx tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"testing",
|
|
19
|
+
"automation",
|
|
20
|
+
"e2e",
|
|
21
|
+
"computer-vision",
|
|
22
|
+
"ai",
|
|
23
|
+
"qa",
|
|
24
|
+
"visual-testing"
|
|
25
|
+
],
|
|
26
|
+
"author": "Vouch Team",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/HackX-IN/vouch.git"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"examples",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"CONTRIBUTING.md"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@anthropic-ai/sdk": "^0.32.1",
|
|
41
|
+
"@clack/prompts": "^1.6.0",
|
|
42
|
+
"@google/generative-ai": "^0.21.0",
|
|
43
|
+
"boxen": "^8.0.1",
|
|
44
|
+
"chalk": "^5.3.0",
|
|
45
|
+
"commander": "^12.1.0",
|
|
46
|
+
"figures": "^6.1.0",
|
|
47
|
+
"openai": "^4.73.0",
|
|
48
|
+
"ora": "^8.1.0",
|
|
49
|
+
"puppeteer": "^23.7.0",
|
|
50
|
+
"puppeteer-screen-recorder": "^3.0.6"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"typescript": "^5.6.0",
|
|
54
|
+
"tsup": "^8.3.0",
|
|
55
|
+
"@types/node": "^22.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|