@bbearai/ai-executor 0.2.0 → 0.2.1

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/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/runner.ts","../src/browser.ts","../src/evaluator.ts"],"sourcesContent":["/**\n * CLI entrypoint for @bbearai/ai-executor\n *\n * Usage:\n * npx @bbearai/ai-executor --url https://staging.app.com --test-case-id abc123\n *\n * Or with a local test case JSON:\n * npx @bbearai/ai-executor --url https://staging.app.com --test-file ./test.json\n */\n\nimport { createClient } from '@supabase/supabase-js';\nimport { runTest } from './runner';\nimport type { TestCaseInput, AuthConfig, BrowserConfig } from './types';\n\ninterface CliArgs {\n url: string;\n testCaseId?: string;\n testFile?: string;\n projectId?: string;\n headless: boolean;\n provider: 'local' | 'browserbase';\n cookies?: string;\n localStorage?: string;\n}\n\nfunction parseArgs(): CliArgs {\n const args = process.argv.slice(2);\n const parsed: CliArgs = {\n url: '',\n headless: true,\n provider: 'local',\n };\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--url':\n parsed.url = args[++i] || '';\n break;\n case '--test-case-id':\n parsed.testCaseId = args[++i];\n break;\n case '--test-file':\n parsed.testFile = args[++i];\n break;\n case '--project-id':\n parsed.projectId = args[++i];\n break;\n case '--headless':\n parsed.headless = args[++i] !== 'false';\n break;\n case '--provider':\n parsed.provider = args[++i] as 'local' | 'browserbase';\n break;\n case '--cookies':\n parsed.cookies = args[++i];\n break;\n case '--local-storage':\n parsed.localStorage = args[++i];\n break;\n case '--help':\n printHelp();\n process.exit(0);\n }\n }\n\n return parsed;\n}\n\nfunction printHelp(): void {\n console.log(`\n@bbearai/ai-executor - AI-powered QA test executor\n\nUsage:\n bbear-execute --url <target-url> --test-case-id <id> [options]\n bbear-execute --url <target-url> --test-file <path> [options]\n\nRequired:\n --url <url> Target application URL\n\nTest Source (one required):\n --test-case-id <id> Fetch test case from BugBear (requires SUPABASE_URL, SUPABASE_ANON_KEY)\n --test-file <path> Path to a JSON test case file\n\nOptions:\n --project-id <id> BugBear project ID (for fetching test cases)\n --headless <true|false> Run browser headlessly (default: true)\n --provider <provider> Browser provider: local or browserbase (default: local)\n --cookies <json> JSON array of cookies for authentication\n --local-storage <json> JSON object of localStorage items for authentication\n --help Show this help message\n\nEnvironment Variables:\n ANTHROPIC_API_KEY Required. Claude API key for AI interpretation.\n SUPABASE_URL Required when using --test-case-id. BugBear Supabase URL.\n SUPABASE_ANON_KEY Required when using --test-case-id. BugBear Supabase anon key.\n BROWSERBASE_API_KEY Required when --provider=browserbase.\n BROWSERBASE_PROJECT_ID Required when --provider=browserbase.\n\nExamples:\n # Run with a local test file\n bbear-execute --url https://staging.myapp.com --test-file ./login-test.json --headless false\n\n # Run with a BugBear test case\n bbear-execute --url https://staging.myapp.com --test-case-id abc-123 --project-id xyz-789\n\n # Run with cookie auth\n bbear-execute --url https://staging.myapp.com --test-file ./test.json \\\\\n --cookies '[{\"name\":\"session\",\"value\":\"abc123\",\"domain\":\".myapp.com\"}]'\n`);\n}\n\nasync function fetchTestCase(testCaseId: string): Promise<TestCaseInput> {\n const supabaseUrl = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseKey = process.env.SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseKey) {\n throw new Error('SUPABASE_URL and SUPABASE_ANON_KEY are required to fetch test cases');\n }\n\n const supabase = createClient(supabaseUrl, supabaseKey);\n const { data, error } = await supabase\n .from('test_cases')\n .select('id, title, description, steps, expected_result, preconditions, target_route, estimated_minutes')\n .eq('id', testCaseId)\n .single();\n\n if (error || !data) {\n throw new Error(`Failed to fetch test case ${testCaseId}: ${error?.message ?? 'Not found'}`);\n }\n\n return {\n id: data.id,\n title: data.title,\n description: data.description ?? undefined,\n steps: data.steps as TestCaseInput['steps'],\n preconditions: data.preconditions ?? undefined,\n targetRoute: data.target_route ?? undefined,\n estimatedMinutes: data.estimated_minutes ?? undefined,\n };\n}\n\nasync function loadTestFile(path: string): Promise<TestCaseInput> {\n const fs = await import('fs');\n const content = fs.readFileSync(path, 'utf-8');\n return JSON.parse(content) as TestCaseInput;\n}\n\nfunction parseAuth(args: CliArgs): AuthConfig | undefined {\n if (args.cookies) {\n try {\n return { type: 'cookie', cookies: JSON.parse(args.cookies) };\n } catch {\n console.error('Failed to parse --cookies JSON');\n process.exit(1);\n }\n }\n if (args.localStorage) {\n try {\n return { type: 'localStorage', items: JSON.parse(args.localStorage) };\n } catch {\n console.error('Failed to parse --local-storage JSON');\n process.exit(1);\n }\n }\n return undefined;\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs();\n\n if (!args.url) {\n console.error('Error: --url is required');\n printHelp();\n process.exit(1);\n }\n\n if (!args.testCaseId && !args.testFile) {\n console.error('Error: Either --test-case-id or --test-file is required');\n printHelp();\n process.exit(1);\n }\n\n const anthropicApiKey = process.env.ANTHROPIC_API_KEY;\n if (!anthropicApiKey) {\n console.error('Error: ANTHROPIC_API_KEY environment variable is required');\n process.exit(1);\n }\n\n // Load test case\n console.log('Loading test case...');\n const testCase = args.testCaseId\n ? await fetchTestCase(args.testCaseId)\n : await loadTestFile(args.testFile!);\n\n console.log(`Test: ${testCase.title}`);\n console.log(`Steps: ${testCase.steps.length}`);\n console.log(`Target: ${args.url}${testCase.targetRoute ?? ''}`);\n console.log('');\n\n // Build browser config\n const browser: BrowserConfig = {\n provider: args.provider,\n headless: args.headless,\n browserbaseApiKey: process.env.BROWSERBASE_API_KEY,\n browserbaseProjectId: process.env.BROWSERBASE_PROJECT_ID,\n };\n\n // Run the test\n const result = await runTest({\n targetUrl: args.url,\n testCase,\n auth: parseAuth(args),\n browser,\n anthropicApiKey,\n onStepComplete: (step, index, total) => {\n const icon = step.passed ? '\\u2705' : '\\u274C';\n const confidence = Math.round(step.confidence * 100);\n console.log(\n `${icon} Step ${index + 1}/${total}: ${step.action.slice(0, 60)}... ` +\n `[${step.passed ? 'PASS' : 'FAIL'} ${confidence}%] (${step.durationMs}ms)`\n );\n if (!step.passed) {\n console.log(` Expected: ${step.expectedResult.slice(0, 80)}`);\n console.log(` Actual: ${step.actualResult.slice(0, 80)}`);\n }\n if (step.error) {\n console.log(` Error: ${step.error.slice(0, 80)}`);\n }\n },\n onStatusChange: (status) => {\n if (status !== 'executing' && status !== 'evaluating') {\n console.log(`[${status}]`);\n }\n },\n });\n\n // Print summary\n console.log('');\n console.log('='.repeat(60));\n console.log(`Result: ${result.overallResult.toUpperCase()}`);\n console.log(`Duration: ${Math.round(result.totalDurationMs / 1000)}s`);\n console.log(\n `Steps: ${result.steps.filter((s) => s.passed).length} passed, ` +\n `${result.steps.filter((s) => !s.passed).length} failed`\n );\n console.log(`Tokens: ${result.tokenUsage.inputTokens} in / ${result.tokenUsage.outputTokens} out`);\n console.log('');\n console.log('Summary:');\n console.log(result.summary);\n console.log('='.repeat(60));\n\n // Exit with appropriate code\n process.exit(result.overallResult === 'passed' ? 0 : 1);\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n","/**\n * Test Runner\n *\n * Orchestrates the full test execution lifecycle using Stagehand:\n * 1. Launch Stagehand browser session\n * 2. Navigate to target URL\n * 3. Inject authentication\n * 4. For each step: act() → screenshot → extract() → record\n * 5. Generate summary\n * 6. Return structured results\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { z } from 'zod';\nimport { createStagehandSession, injectAuth } from './browser';\nimport { generateRunSummary } from './evaluator';\nimport type {\n TestRunConfig,\n TestRunResult,\n StepResult,\n OverallResult,\n BrowserConfig,\n ConsoleEntry,\n NetworkError,\n} from './types';\n\n/**\n * Execute a test case against a target URL using Stagehand.\n */\nexport async function runTest(config: TestRunConfig): Promise<TestRunResult> {\n const anthropic = new Anthropic({ apiKey: config.anthropicApiKey });\n const startTime = Date.now();\n\n const browserConfig: BrowserConfig = config.browser ?? {\n provider: 'local',\n headless: true,\n };\n\n config.onStatusChange?.('initializing');\n\n const session = await createStagehandSession(browserConfig, config.anthropicApiKey);\n const { stagehand, page } = session;\n\n const stepResults: StepResult[] = [];\n\n // Accumulated console logs and network errors — drained per step\n let pendingConsoleLogs: ConsoleEntry[] = [];\n let pendingNetworkErrors: NetworkError[] = [];\n let stepStartTime = Date.now();\n\n // Cast to any for event listeners — Stagehand's Page type only exposes 'console'\n // but the underlying Playwright page supports all standard events\n const rawPage = page as any;\n\n // Listen for console messages\n rawPage.on('console', (msg: any) => {\n const level = msg.type?.() ?? msg.type ?? 'log';\n const mappedLevel =\n level === 'error' ? 'error' :\n level === 'warn' || level === 'warning' ? 'warning' :\n level === 'info' ? 'info' :\n level === 'debug' ? 'debug' : 'log';\n\n pendingConsoleLogs.push({\n level: mappedLevel as ConsoleEntry['level'],\n text: (typeof msg.text === 'function' ? msg.text() : String(msg.text ?? msg)).slice(0, 2000),\n source: typeof msg.location === 'function' ? msg.location()?.url : undefined,\n timestamp: Date.now() - stepStartTime,\n });\n });\n\n // Listen for failed requests (connection errors, CORS, etc.)\n rawPage.on('requestfailed', (req: any) => {\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - stepStartTime,\n });\n });\n\n // Listen for HTTP error responses (4xx/5xx)\n rawPage.on('response', (res: any) => {\n const status = typeof res.status === 'function' ? res.status() : Number(res.status ?? 0);\n if (status >= 400) {\n const url = typeof res.url === 'function' ? res.url() : String(res.url ?? '');\n const statusText = typeof res.statusText === 'function' ? res.statusText() : String(res.statusText ?? '');\n const req = typeof res.request === 'function' ? res.request() : res.request;\n const method = req ? (typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET')) : 'GET';\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status,\n statusText,\n timestamp: Date.now() - stepStartTime,\n });\n }\n });\n\n try {\n // Handle form-login auth first (navigates to login page, then we go to target)\n if (config.auth?.type === 'form-login') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n }\n\n // Navigate to target URL\n config.onStatusChange?.('navigating');\n const targetUrl = config.testCase.targetRoute\n ? `${config.targetUrl.replace(/\\/$/, '')}${config.testCase.targetRoute}`\n : config.targetUrl;\n\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n\n // Inject non-form authentication after navigation\n if (config.auth && config.auth.type !== 'form-login') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n\n // If localStorage auth, reload to apply\n if (config.auth.type === 'localStorage') {\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, config.auth.items);\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n }\n\n // Wait for page to stabilize\n await page.waitForLoadState('networkidle').catch(() => {});\n\n // Clear any logs accumulated during navigation/auth\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Execute each step using Stagehand\n config.onStatusChange?.('executing');\n const steps = config.testCase.steps;\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n stepStartTime = Date.now();\n\n // Reset per-step collectors\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Capture pre-action screenshot\n const screenshotBefore = await page.screenshot({ type: 'png' });\n\n let error: string | undefined;\n let screenshotAfter: Buffer = screenshotBefore;\n let actSucceeded = false;\n\n // Execute the action using Stagehand\n try {\n await stagehand.act(step.action);\n actSucceeded = true;\n\n // Wait for page to settle after action\n await page.waitForLoadState('networkidle').catch(() => {});\n await page.waitForTimeout(500);\n\n // Capture post-action screenshot\n screenshotAfter = await page.screenshot({ type: 'png' });\n } catch (err) {\n error = err instanceof Error ? err.message : String(err);\n // Still capture screenshot for debugging even after failure\n screenshotAfter = await page.screenshot({ type: 'png' }).catch(() => screenshotBefore);\n }\n\n // Evaluate the result using Stagehand's extract()\n let evaluation = {\n passed: false,\n confidence: 0,\n actualResult: error ?? 'Action execution failed',\n };\n\n if (actSucceeded) {\n try {\n const verificationSchema = z.object({\n passed: z.boolean().describe('Whether the expected result was achieved'),\n confidence: z\n .number()\n .min(0)\n .max(1)\n .describe('Confidence in the assessment (0.9+ = very sure, 0.7-0.9 = likely, below 0.7 = uncertain)'),\n actualResult: z.string().describe('Description of what actually happened on the page'),\n });\n\n const verification = await stagehand.extract(\n `You are evaluating a QA test step. The action \"${step.action}\" was just performed. ` +\n `Check if this expected result was achieved: \"${step.expectedResult}\". ` +\n `Look at the current page state and describe what actually happened. ` +\n `Be precise and factual in your assessment.`,\n verificationSchema,\n );\n\n evaluation = {\n passed: verification.passed,\n confidence: verification.confidence,\n actualResult: verification.actualResult,\n };\n } catch (evalErr) {\n evaluation = {\n passed: false,\n confidence: 0.2,\n actualResult: `Verification error: ${evalErr instanceof Error ? evalErr.message : String(evalErr)}`,\n };\n }\n }\n\n // Snapshot the collected logs for this step (limit to 50 entries each to avoid bloat)\n const consoleLogs = pendingConsoleLogs.slice(0, 50);\n const networkErrors = pendingNetworkErrors.slice(0, 30);\n\n const result: StepResult = {\n stepNumber: step.stepNumber,\n action: step.action,\n expectedResult: step.expectedResult,\n actualResult: evaluation.actualResult,\n passed: evaluation.passed,\n confidence: evaluation.confidence,\n screenshotBefore,\n screenshotAfter,\n actionsTaken: [], // Stagehand handles actions internally\n error,\n durationMs: Date.now() - stepStartTime,\n consoleLogs,\n networkErrors,\n };\n\n stepResults.push(result);\n config.onStepComplete?.(result, i, steps.length);\n }\n\n // Generate summary\n config.onStatusChange?.('completed');\n const model = config.model ?? 'claude-sonnet-4-20250514';\n const summary = await generateRunSummary(anthropic, config.testCase.title, stepResults, model);\n\n // Determine overall result\n const overallResult = determineOverallResult(stepResults);\n\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult,\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary,\n screenshotUrls: [],\n tokenUsage: {\n // Stagehand tracks tokens internally; these are approximate\n inputTokens: steps.length * 3000,\n outputTokens: steps.length * 500,\n },\n browserSessionId: session.sessionId,\n };\n } catch (err) {\n // Fatal error — test couldn't complete\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult: 'error',\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary: `Test execution failed: ${err instanceof Error ? err.message : String(err)}`,\n screenshotUrls: [],\n tokenUsage: {\n inputTokens: stepResults.length * 3000,\n outputTokens: stepResults.length * 500,\n },\n browserSessionId: session.sessionId,\n };\n } finally {\n await session.close();\n }\n}\n\n/**\n * Determine the overall test result from step results.\n */\nfunction determineOverallResult(steps: StepResult[]): OverallResult {\n if (steps.length === 0) return 'error';\n\n const allPassed = steps.every((s) => s.passed);\n const allFailed = steps.every((s) => !s.passed);\n const hasErrors = steps.some((s) => s.error);\n\n if (allPassed) return 'passed';\n if (allFailed || hasErrors) return 'failed';\n return 'partial';\n}\n","/**\n * Browser Provider\n *\n * Uses Stagehand (by Browserbase) for AI-powered browser automation.\n * Supports both Browserbase hosted sessions and local Playwright.\n */\n\nimport { Stagehand } from '@browserbasehq/stagehand';\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { BrowserConfig, AuthConfig } from './types';\n\nconst DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n\nexport interface StagehandSession {\n stagehand: Stagehand;\n page: Page;\n sessionId: string;\n close: () => Promise<void>;\n}\n\n/**\n * Create a Stagehand-powered browser session.\n *\n * Stagehand wraps Playwright with AI capabilities (act, extract, observe)\n * and manages Browserbase or local browser sessions.\n */\nexport async function createStagehandSession(\n config: BrowserConfig,\n anthropicApiKey: string,\n): Promise<StagehandSession> {\n const modelName = config.model ?? DEFAULT_MODEL;\n const viewport = config.viewport ?? { width: 1280, height: 720 };\n\n const stagehand = new Stagehand({\n env: config.provider === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',\n apiKey: config.provider === 'browserbase' ? config.browserbaseApiKey : undefined,\n projectId: config.provider === 'browserbase' ? config.browserbaseProjectId : undefined,\n model: {\n modelName: modelName as any,\n apiKey: anthropicApiKey,\n },\n localBrowserLaunchOptions:\n config.provider === 'local'\n ? {\n headless: config.headless ?? true,\n viewport,\n }\n : undefined,\n browserbaseSessionCreateParams:\n config.provider === 'browserbase'\n ? { projectId: config.browserbaseProjectId! }\n : undefined,\n });\n\n await stagehand.init();\n\n const page = stagehand.context.activePage()!;\n await page.setViewportSize(viewport.width, viewport.height);\n\n // Extract session ID\n let sessionId = `local-${Date.now()}`;\n if (config.provider === 'browserbase' && stagehand.browserbaseSessionID) {\n sessionId = stagehand.browserbaseSessionID;\n }\n\n return {\n stagehand,\n page,\n sessionId,\n close: async () => {\n await stagehand.close().catch(() => {});\n },\n };\n}\n\n/**\n * Inject authentication into the browser session.\n * Uses Stagehand's Page API and CDP for cookie injection.\n */\nexport async function injectAuth(page: Page, auth: AuthConfig, stagehand?: Stagehand): Promise<void> {\n if (auth.type === 'cookie') {\n // Use CDP to set cookies (Stagehand's Page doesn't have context().addCookies)\n for (const c of auth.cookies) {\n await page.sendCDP('Network.setCookie', {\n name: c.name,\n value: c.value,\n domain: c.domain,\n path: c.path ?? '/',\n secure: c.secure ?? false,\n httpOnly: c.httpOnly ?? false,\n sameSite: c.sameSite ?? 'Lax',\n });\n }\n } else if (auth.type === 'localStorage') {\n const currentUrl = page.url();\n if (currentUrl === 'about:blank') {\n // We'll inject localStorage after navigation in the runner\n return;\n }\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, auth.items);\n } else if (auth.type === 'form-login') {\n await performFormLogin(page, auth, stagehand);\n }\n}\n\n/**\n * Automate login via a web form.\n *\n * Uses Stagehand's act() when available for AI-powered form filling,\n * falling back to manual locator-based login.\n */\nasync function performFormLogin(\n page: Page,\n auth: { loginUrl: string; email: string; password: string },\n stagehand?: Stagehand,\n): Promise<void> {\n await page.goto(auth.loginUrl, { waitUntil: 'domcontentloaded' });\n\n // Wait for the page to settle\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n\n if (stagehand) {\n // Use Stagehand's AI to handle login — much more reliable\n await stagehand.act(\n `Fill in the email/username field with \"${auth.email}\" and the password field with \"${auth.password}\", then click the login/sign-in button to submit the form.`,\n );\n } else {\n // Fallback: manual locator-based login\n await manualFormLogin(page, auth);\n }\n\n // Wait for navigation after login\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n}\n\n/**\n * Manual form login using locators (fallback when Stagehand instance unavailable).\n */\nasync function manualFormLogin(\n page: Page,\n auth: { email: string; password: string },\n): Promise<void> {\n // Wait for a form or input to appear\n await page\n .waitForSelector(\n 'input[type=\"email\"], input[type=\"text\"][name*=\"email\"], input[name*=\"user\"], input[type=\"text\"]',\n { timeout: 15_000 },\n )\n .catch(() => {});\n\n // Try common email/username selectors in priority order\n const emailSelectors = [\n 'input[type=\"email\"]',\n 'input[name=\"email\"]',\n 'input[name=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[autocomplete=\"username\"]',\n 'input[type=\"text\"][name*=\"email\"]',\n 'input[type=\"text\"][name*=\"user\"]',\n 'input[type=\"text\"]',\n ];\n\n let emailFilled = false;\n for (const sel of emailSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.fill(auth.email);\n emailFilled = true;\n break;\n }\n }\n\n if (!emailFilled) {\n throw new Error('Could not find email/username input on login page');\n }\n\n // Fill password\n const passwordLocator = page.locator('input[type=\"password\"]');\n if ((await passwordLocator.count()) > 0 && (await passwordLocator.isVisible())) {\n await passwordLocator.fill(auth.password);\n } else {\n throw new Error('Could not find password input on login page');\n }\n\n // Submit the form\n const submitSelectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n ];\n\n let submitted = false;\n for (const sel of submitSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.click();\n submitted = true;\n break;\n }\n }\n\n if (!submitted) {\n // Fallback: press Enter on the password field\n await page.locator('input[type=\"password\"]').type('\\n');\n }\n}\n","/**\n * Result Evaluator\n *\n * Generates AI summaries of test run results.\n * Step-level evaluation is now handled by Stagehand's extract() in the runner.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\n/**\n * Generate an AI summary of the entire test run.\n */\nexport async function generateRunSummary(\n anthropic: Anthropic,\n testTitle: string,\n steps: Array<{\n stepNumber: number;\n action: string;\n expectedResult: string;\n actualResult: string;\n passed: boolean;\n confidence: number;\n error?: string;\n }>,\n model: string,\n): Promise<string> {\n const stepsText = steps\n .map(\n (s) =>\n `Step ${s.stepNumber}: ${s.action}\\n Expected: ${s.expectedResult}\\n Actual: ${s.actualResult}\\n Result: ${s.passed ? 'PASS' : 'FAIL'} (confidence: ${Math.round(s.confidence * 100)}%)${s.error ? `\\n Error: ${s.error}` : ''}`\n )\n .join('\\n\\n');\n\n const passCount = steps.filter((s) => s.passed).length;\n const failCount = steps.filter((s) => !s.passed).length;\n\n const response = await anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: `Summarize this AI test execution in 2-3 sentences. Focus on what was tested, what passed, and what failed (if anything). Be concise and factual.\n\nTest: ${testTitle}\nResults: ${passCount} passed, ${failCount} failed out of ${steps.length} steps\n\n${stepsText}`,\n },\n ],\n });\n\n return response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n}\n"],"mappings":"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","names":["Anthropic"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/runner.ts","../src/browser.ts","../src/supabase-auth.ts","../src/evaluator.ts","../src/vision-evaluator.ts","../src/action-executor.ts","../src/selector-discovery.ts","../src/cost.ts"],"sourcesContent":["/**\n * CLI entrypoint for @bbearai/ai-executor\n *\n * Usage:\n * npx @bbearai/ai-executor --url https://staging.app.com --test-case-id abc123\n *\n * Or with a local test case JSON:\n * npx @bbearai/ai-executor --url https://staging.app.com --test-file ./test.json\n */\n\nimport { createClient } from '@supabase/supabase-js';\nimport { runTest } from './runner';\nimport type { TestCaseInput, AuthConfig, BrowserConfig } from './types';\n\ninterface CliArgs {\n url: string;\n testCaseId?: string;\n testFile?: string;\n projectId?: string;\n headless: boolean;\n provider: 'local' | 'browserbase';\n cookies?: string;\n localStorage?: string;\n}\n\nfunction parseArgs(): CliArgs {\n const args = process.argv.slice(2);\n const parsed: CliArgs = {\n url: '',\n headless: true,\n provider: 'local',\n };\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--url':\n parsed.url = args[++i] || '';\n break;\n case '--test-case-id':\n parsed.testCaseId = args[++i];\n break;\n case '--test-file':\n parsed.testFile = args[++i];\n break;\n case '--project-id':\n parsed.projectId = args[++i];\n break;\n case '--headless':\n parsed.headless = args[++i] !== 'false';\n break;\n case '--provider':\n parsed.provider = args[++i] as 'local' | 'browserbase';\n break;\n case '--cookies':\n parsed.cookies = args[++i];\n break;\n case '--local-storage':\n parsed.localStorage = args[++i];\n break;\n case '--help':\n printHelp();\n process.exit(0);\n }\n }\n\n return parsed;\n}\n\nfunction printHelp(): void {\n console.log(`\n@bbearai/ai-executor - AI-powered QA test executor\n\nUsage:\n bbear-execute --url <target-url> --test-case-id <id> [options]\n bbear-execute --url <target-url> --test-file <path> [options]\n\nRequired:\n --url <url> Target application URL\n\nTest Source (one required):\n --test-case-id <id> Fetch test case from BugBear (requires SUPABASE_URL, SUPABASE_ANON_KEY)\n --test-file <path> Path to a JSON test case file\n\nOptions:\n --project-id <id> BugBear project ID (for fetching test cases)\n --headless <true|false> Run browser headlessly (default: true)\n --provider <provider> Browser provider: local or browserbase (default: local)\n --cookies <json> JSON array of cookies for authentication\n --local-storage <json> JSON object of localStorage items for authentication\n --help Show this help message\n\nEnvironment Variables:\n ANTHROPIC_API_KEY Required. Claude API key for AI interpretation.\n SUPABASE_URL Required when using --test-case-id. BugBear Supabase URL.\n SUPABASE_ANON_KEY Required when using --test-case-id. BugBear Supabase anon key.\n BROWSERBASE_API_KEY Required when --provider=browserbase.\n BROWSERBASE_PROJECT_ID Required when --provider=browserbase.\n\nExamples:\n # Run with a local test file\n bbear-execute --url https://staging.myapp.com --test-file ./login-test.json --headless false\n\n # Run with a BugBear test case\n bbear-execute --url https://staging.myapp.com --test-case-id abc-123 --project-id xyz-789\n\n # Run with cookie auth\n bbear-execute --url https://staging.myapp.com --test-file ./test.json \\\\\n --cookies '[{\"name\":\"session\",\"value\":\"abc123\",\"domain\":\".myapp.com\"}]'\n`);\n}\n\nasync function fetchTestCase(testCaseId: string): Promise<TestCaseInput> {\n const supabaseUrl = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseKey = process.env.SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseKey) {\n throw new Error('SUPABASE_URL and SUPABASE_ANON_KEY are required to fetch test cases');\n }\n\n const supabase = createClient(supabaseUrl, supabaseKey);\n const { data, error } = await supabase\n .from('test_cases')\n .select('id, title, description, steps, expected_result, preconditions, target_route, estimated_minutes')\n .eq('id', testCaseId)\n .single();\n\n if (error || !data) {\n throw new Error(`Failed to fetch test case ${testCaseId}: ${error?.message ?? 'Not found'}`);\n }\n\n return {\n id: data.id,\n title: data.title,\n description: data.description ?? undefined,\n steps: data.steps as TestCaseInput['steps'],\n preconditions: data.preconditions ?? undefined,\n targetRoute: data.target_route ?? undefined,\n estimatedMinutes: data.estimated_minutes ?? undefined,\n };\n}\n\nasync function loadTestFile(path: string): Promise<TestCaseInput> {\n const fs = await import('fs');\n const content = fs.readFileSync(path, 'utf-8');\n return JSON.parse(content) as TestCaseInput;\n}\n\nfunction parseAuth(args: CliArgs): AuthConfig | undefined {\n if (args.cookies) {\n try {\n return { type: 'cookie', cookies: JSON.parse(args.cookies) };\n } catch {\n console.error('Failed to parse --cookies JSON');\n process.exit(1);\n }\n }\n if (args.localStorage) {\n try {\n return { type: 'localStorage', items: JSON.parse(args.localStorage) };\n } catch {\n console.error('Failed to parse --local-storage JSON');\n process.exit(1);\n }\n }\n return undefined;\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs();\n\n if (!args.url) {\n console.error('Error: --url is required');\n printHelp();\n process.exit(1);\n }\n\n if (!args.testCaseId && !args.testFile) {\n console.error('Error: Either --test-case-id or --test-file is required');\n printHelp();\n process.exit(1);\n }\n\n const anthropicApiKey = process.env.ANTHROPIC_API_KEY;\n if (!anthropicApiKey) {\n console.error('Error: ANTHROPIC_API_KEY environment variable is required');\n process.exit(1);\n }\n\n // Load test case\n console.log('Loading test case...');\n const testCase = args.testCaseId\n ? await fetchTestCase(args.testCaseId)\n : await loadTestFile(args.testFile!);\n\n console.log(`Test: ${testCase.title}`);\n console.log(`Steps: ${testCase.steps.length}`);\n console.log(`Target: ${args.url}${testCase.targetRoute ?? ''}`);\n console.log('');\n\n // Build browser config\n const browser: BrowserConfig = {\n provider: args.provider,\n headless: args.headless,\n browserbaseApiKey: process.env.BROWSERBASE_API_KEY,\n browserbaseProjectId: process.env.BROWSERBASE_PROJECT_ID,\n };\n\n // Run the test\n const result = await runTest({\n targetUrl: args.url,\n testCase,\n auth: parseAuth(args),\n browser,\n anthropicApiKey,\n onStepComplete: (step, index, total) => {\n const icon = step.passed ? '\\u2705' : '\\u274C';\n const confidence = Math.round(step.confidence * 100);\n console.log(\n `${icon} Step ${index + 1}/${total}: ${step.action.slice(0, 60)}... ` +\n `[${step.passed ? 'PASS' : 'FAIL'} ${confidence}%] (${step.durationMs}ms)`\n );\n if (!step.passed) {\n console.log(` Expected: ${step.expectedResult.slice(0, 80)}`);\n console.log(` Actual: ${step.actualResult.slice(0, 80)}`);\n }\n if (step.error) {\n console.log(` Error: ${step.error.slice(0, 80)}`);\n }\n },\n onStatusChange: (status) => {\n if (status !== 'executing' && status !== 'evaluating') {\n console.log(`[${status}]`);\n }\n },\n });\n\n // Print summary\n console.log('');\n console.log('='.repeat(60));\n console.log(`Result: ${result.overallResult.toUpperCase()}`);\n console.log(`Duration: ${Math.round(result.totalDurationMs / 1000)}s`);\n console.log(\n `Steps: ${result.steps.filter((s) => s.passed).length} passed, ` +\n `${result.steps.filter((s) => !s.passed).length} failed`\n );\n console.log(`Tokens: ${result.tokenUsage.inputTokens} in / ${result.tokenUsage.outputTokens} out`);\n console.log('');\n console.log('Summary:');\n console.log(result.summary);\n console.log('='.repeat(60));\n\n // Exit with appropriate code\n process.exit(result.overallResult === 'passed' ? 0 : 1);\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n","/**\n * Test Runner\n *\n * Orchestrates the full test execution lifecycle:\n * 1. Launch Stagehand browser session\n * 2. Navigate to target URL\n * 3. Inject authentication (supports supabase-native, cookie, localStorage, form-login)\n * 4. For each step: act() → screenshot → vision evaluate → record\n * 5. Generate summary\n * 6. Return structured results\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport { createStagehandSession, injectAuth, suppressBugBearWidget } from './browser';\nimport { generateRunSummary } from './evaluator';\nimport { evaluateStep } from './vision-evaluator';\nimport { executeAction } from './action-executor';\nimport { discoverSelector, installClickTracker } from './selector-discovery';\nimport { getTokenEstimate } from './cost';\nimport type {\n TestRunConfig,\n TestRunResult,\n StepResult,\n OverallResult,\n BrowserConfig,\n ConsoleEntry,\n NetworkError,\n RetryAttempt,\n} from './types';\n\n/** Default timeout for AI operations (30 seconds) */\nconst AI_OPERATION_TIMEOUT_MS = 30_000;\n\n/** Default retry settings */\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_RETRY_DELAY_MS = 2_000;\n\n/**\n * Detect if an error is transient and worth retrying.\n * Only retries infrastructure-level failures, not evaluation disagreements.\n */\nfunction isRetryableError(error: string): boolean {\n const patterns = [\n /timed?\\s*out/i,\n /ECONNREFUSED/i,\n /ECONNRESET/i,\n /ENOTFOUND/i,\n /net::ERR_/i,\n /navigation failed/i,\n /page crashed/i,\n /context was destroyed/i,\n /target closed/i,\n /session closed/i,\n /browser disconnected/i,\n /execution context/i,\n ];\n return patterns.some(p => p.test(error));\n}\n\n/** Wrap a promise with a timeout */\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`${operation} timed out after ${timeoutMs}ms`)), timeoutMs);\n });\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n}\n\n/**\n * Execute a test case against a target URL using Stagehand.\n */\nexport async function runTest(config: TestRunConfig): Promise<TestRunResult> {\n const anthropic = new Anthropic({ apiKey: config.anthropicApiKey });\n const startTime = Date.now();\n\n const browserConfig: BrowserConfig = config.browser ?? {\n provider: 'local',\n headless: true,\n };\n\n config.onStatusChange?.('initializing');\n\n let session: Awaited<ReturnType<typeof createStagehandSession>> | undefined;\n\n const stepResults: StepResult[] = [];\n\n // Accumulated console logs and network errors — drained per step\n let pendingConsoleLogs: ConsoleEntry[] = [];\n let pendingNetworkErrors: NetworkError[] = [];\n let stepStartTime = Date.now();\n\n try {\n session = await createStagehandSession(browserConfig, config.anthropicApiKey);\n const { stagehand, page } = session;\n\n // Suppress the BugBear widget to prevent it from intercepting clicks,\n // covering UI elements, or triggering popups during test execution.\n await suppressBugBearWidget(stagehand);\n\n // Stagehand's Page wrapper only supports a subset of Playwright events.\n // All listeners are wrapped in try/catch — unsupported events throw\n // InvalidArgumentError at registration time. Core test execution\n // (navigate, screenshot, Claude analysis) works without these.\n const rawPage = page as any;\n\n // Console messages — usually supported by Stagehand\n try {\n rawPage.on('console', (msg: any) => {\n const level = msg.type?.() ?? msg.type ?? 'log';\n const mappedLevel =\n level === 'error' ? 'error' :\n level === 'warn' || level === 'warning' ? 'warning' :\n level === 'info' ? 'info' :\n level === 'debug' ? 'debug' : 'log';\n\n pendingConsoleLogs.push({\n level: mappedLevel as ConsoleEntry['level'],\n text: (typeof msg.text === 'function' ? msg.text() : String(msg.text ?? msg)).slice(0, 2000),\n source: typeof msg.location === 'function' ? msg.location()?.url : undefined,\n timestamp: Date.now() - stepStartTime,\n });\n });\n } catch {\n // Console capture unavailable\n }\n\n // Failed requests (connection errors, CORS, etc.)\n try {\n rawPage.on('requestfailed', (req: any) => {\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - stepStartTime,\n });\n });\n } catch {\n // requestfailed capture unavailable\n }\n\n // HTTP error responses (4xx/5xx)\n try {\n rawPage.on('response', (res: any) => {\n const status = typeof res.status === 'function' ? res.status() : Number(res.status ?? 0);\n if (status >= 400) {\n const url = typeof res.url === 'function' ? res.url() : String(res.url ?? '');\n const statusText = typeof res.statusText === 'function' ? res.statusText() : String(res.statusText ?? '');\n const req = typeof res.request === 'function' ? res.request() : res.request;\n const method = req ? (typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET')) : 'GET';\n pendingNetworkErrors.push({\n method,\n url: url.slice(0, 500),\n status,\n statusText,\n timestamp: Date.now() - stepStartTime,\n });\n }\n });\n } catch {\n // HTTP error capture unavailable\n }\n // Handle pre-navigation auth (form-login and supabase-native inject before navigation)\n if (config.auth?.type === 'form-login' || config.auth?.type === 'supabase-native') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n }\n\n // Navigate to target URL\n config.onStatusChange?.('navigating');\n const targetUrl = config.testCase.targetRoute\n ? `${config.targetUrl.replace(/\\/$/, '')}${config.testCase.targetRoute}`\n : config.targetUrl;\n\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n\n // Inject post-navigation auth (cookie, localStorage)\n if (config.auth && config.auth.type !== 'form-login' && config.auth.type !== 'supabase-native') {\n config.onStatusChange?.('authenticating');\n await injectAuth(page, config.auth, stagehand);\n\n // If localStorage auth, reload to apply\n if (config.auth.type === 'localStorage') {\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, config.auth.items);\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n }\n\n // Wait for page to stabilize\n await page.waitForLoadState('networkidle').catch(() => {});\n\n // Belt-and-suspenders: also set the suppression flag via evaluate in case\n // addInitScript wasn't available in the Stagehand/Playwright version.\n await page.evaluate(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n }).catch(() => {});\n\n // Install click tracker for selector discovery\n await installClickTracker(page);\n\n // Clear any logs accumulated during navigation/auth\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Execute each step (deterministic Playwright when possible, Stagehand fallback)\n config.onStatusChange?.('executing');\n const steps = config.testCase.steps;\n const maxRetries = config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES;\n const retryDelayMs = config.retry?.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;\n const resilientMode = config.resilientMode ?? true;\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n const retryHistory: RetryAttempt[] = [];\n let finalResult: StepResult | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n stepStartTime = Date.now();\n\n // Reset per-step collectors\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n\n // Capture pre-action screenshot\n const screenshotBefore = await page.screenshot({ type: 'png' });\n\n let error: string | undefined;\n let screenshotAfter: Buffer = screenshotBefore;\n let actSucceeded = false;\n\n // Execute the action (deterministic Playwright when possible, Stagehand fallback)\n const actionResult = await executeAction(page, stagehand, step);\n error = actionResult.error;\n actSucceeded = !error;\n\n if (actSucceeded) {\n // Wait for page to settle after action\n await page.waitForLoadState('networkidle').catch(() => {});\n await page.waitForTimeout(step.waitMs ?? 500);\n }\n\n // Capture post-action screenshot\n screenshotAfter = await page.screenshot({ type: 'png' }).catch(() => screenshotBefore);\n\n // Evaluate the result using Claude Vision (before/after screenshots)\n let evaluation = {\n passed: false,\n confidence: 0,\n actualResult: error ?? 'Action execution failed',\n };\n\n if (actSucceeded) {\n try {\n const visionResult = await withTimeout(\n evaluateStep({\n anthropic,\n screenshotBefore,\n screenshotAfter,\n action: step.action,\n expectedResult: step.expectedResult,\n evaluationHint: step.evaluationHint,\n model: config.model,\n }),\n AI_OPERATION_TIMEOUT_MS,\n 'Vision evaluation',\n );\n\n evaluation = {\n passed: visionResult.passed,\n confidence: visionResult.confidence,\n actualResult: visionResult.actualResult,\n };\n } catch (evalErr) {\n evaluation = {\n passed: false,\n confidence: 0.2,\n actualResult: `Vision evaluation error: ${evalErr instanceof Error ? evalErr.message : String(evalErr)}`,\n };\n }\n }\n\n // Selector discovery — when Stagehand handled the step, try to find what was clicked\n let discoveredActions: { type: string; selector?: string; description: string }[] = [];\n if (actSucceeded && !actionResult.deterministic) {\n const discovered = await discoverSelector(page);\n if (discovered) {\n discoveredActions = [{\n type: discovered.suggestedActionType ?? 'click',\n selector: discovered.selector,\n description: `Discovered via ${discovered.strategy}: ${discovered.tagName}${discovered.textContent ? ` \"${discovered.textContent.slice(0, 50)}\"` : ''}`,\n }];\n }\n }\n\n // Snapshot the collected logs for this step\n const consoleLogs = pendingConsoleLogs.slice(0, 50);\n const networkErrors = pendingNetworkErrors.slice(0, 30);\n\n finalResult = {\n stepNumber: step.stepNumber,\n action: step.action,\n expectedResult: step.expectedResult,\n actualResult: evaluation.actualResult,\n passed: evaluation.passed,\n confidence: evaluation.confidence,\n screenshotBefore,\n screenshotAfter,\n actionsTaken: discoveredActions,\n error,\n durationMs: Date.now() - stepStartTime,\n consoleLogs,\n networkErrors,\n retryCount: attempt,\n retryHistory,\n skipped: false,\n };\n\n // Decide whether to retry: only retry transient errors, not evaluation failures\n const shouldRetry = !evaluation.passed\n && error\n && isRetryableError(error)\n && attempt < maxRetries;\n\n if (!shouldRetry) break;\n\n // Record this attempt in retry history before retrying\n retryHistory.push({\n attempt,\n error: error!,\n confidence: evaluation.confidence,\n timestamp: Date.now(),\n });\n\n // Wait before retrying\n await new Promise(resolve => setTimeout(resolve, retryDelayMs));\n }\n\n // ── Skip-and-recover: mark failed steps as skipped, recover page state ──\n if (resilientMode && finalResult && !finalResult.passed) {\n finalResult.skipped = true;\n finalResult.skipReason = 'Step failed, recovered page state';\n\n try {\n config.onStatusChange?.('navigating');\n await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeoutMs: 30_000 });\n await page.waitForLoadState('networkidle').catch(() => {});\n await installClickTracker(page);\n // Re-suppress widget after recovery navigation\n await page.evaluate(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n }).catch(() => {});\n pendingConsoleLogs = [];\n pendingNetworkErrors = [];\n config.onStatusChange?.('executing');\n } catch (recoveryErr) {\n finalResult.skipReason = `Step failed, recovery also failed: ${recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr)}`;\n }\n }\n\n stepResults.push(finalResult!);\n config.onStepComplete?.(finalResult!, i, steps.length);\n }\n\n // Generate summary\n config.onStatusChange?.('completed');\n const model = config.model ?? 'claude-sonnet-4-20250514';\n const summary = await generateRunSummary(anthropic, config.testCase.title, stepResults, model);\n\n // Determine overall result\n const overallResult = determineOverallResult(stepResults);\n\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult,\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary,\n screenshotUrls: [],\n tokenUsage: getTokenEstimate(steps.length),\n browserSessionId: session!.sessionId,\n };\n } catch (err) {\n // Fatal error — test couldn't complete\n return {\n testCaseId: config.testCase.id,\n testCaseTitle: config.testCase.title,\n overallResult: 'error',\n steps: stepResults,\n totalDurationMs: Date.now() - startTime,\n summary: `Test execution failed: ${err instanceof Error ? err.message : String(err)}`,\n screenshotUrls: [],\n tokenUsage: getTokenEstimate(stepResults.length),\n browserSessionId: session?.sessionId ?? 'unknown',\n };\n } finally {\n // Clean up event listeners to prevent memory leaks\n if (session?.page) {\n const rawPage = session.page as any;\n rawPage.removeAllListeners?.('console');\n rawPage.removeAllListeners?.('requestfailed');\n rawPage.removeAllListeners?.('response');\n }\n await session?.close();\n }\n}\n\n/**\n * Determine the overall test result from step results.\n */\nfunction determineOverallResult(steps: StepResult[]): OverallResult {\n if (steps.length === 0) return 'error';\n\n const nonSkipped = steps.filter((s) => !s.skipped);\n const skippedCount = steps.length - nonSkipped.length;\n\n // All steps were skipped — nothing actually ran\n if (nonSkipped.length === 0) return 'error';\n\n const allNonSkippedPassed = nonSkipped.every((s) => s.passed);\n const hasErrors = nonSkipped.some((s) => s.error);\n\n // Some steps skipped, all remaining passed\n if (skippedCount > 0 && allNonSkippedPassed) return 'passed_with_skips';\n\n // Clean pass (no skips)\n if (allNonSkippedPassed) return 'passed';\n\n // All non-skipped failed or had errors\n if (nonSkipped.every((s) => !s.passed) || hasErrors) return 'failed';\n\n // Mixed results among non-skipped steps\n return 'partial';\n}\n","/**\n * Browser Provider\n *\n * Uses Stagehand (by Browserbase) for AI-powered browser automation.\n * Supports both Browserbase hosted sessions and local Playwright.\n */\n\nimport { Stagehand } from '@browserbasehq/stagehand';\nimport type { Page } from '@browserbasehq/stagehand';\nimport { performSupabaseAuth } from './supabase-auth';\nimport type { BrowserConfig, AuthConfig, CapturedRequest, NetworkError } from './types';\n\nconst DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n\nexport interface StagehandSession {\n stagehand: Stagehand;\n page: Page;\n sessionId: string;\n close: () => Promise<void>;\n}\n\n/**\n * Create a Stagehand-powered browser session.\n *\n * Stagehand wraps Playwright with AI capabilities (act, extract, observe)\n * and manages Browserbase or local browser sessions.\n */\nexport async function createStagehandSession(\n config: BrowserConfig,\n anthropicApiKey: string,\n): Promise<StagehandSession> {\n const modelName = config.model ?? DEFAULT_MODEL;\n const viewport = config.viewport ?? { width: 1280, height: 720 };\n\n const stagehand = new Stagehand({\n env: config.provider === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',\n apiKey: config.provider === 'browserbase' ? config.browserbaseApiKey : undefined,\n projectId: config.provider === 'browserbase' ? config.browserbaseProjectId : undefined,\n model: {\n modelName: modelName as any,\n apiKey: anthropicApiKey,\n },\n // Bypass pino logger — its pino-pretty transport uses worker threads\n // which fail in Vercel's serverless environment\n logger: (msg) => {\n if ((msg.level ?? 0) >= 40) console.warn('[Stagehand]', msg.message);\n },\n localBrowserLaunchOptions:\n config.provider === 'local'\n ? {\n headless: config.headless ?? true,\n viewport,\n }\n : undefined,\n browserbaseSessionCreateParams:\n config.provider === 'browserbase'\n ? { projectId: config.browserbaseProjectId! }\n : undefined,\n });\n\n await stagehand.init();\n\n const page = stagehand.context.activePage()!;\n await page.setViewportSize(viewport.width, viewport.height);\n\n // Extract session ID\n let sessionId = `local-${Date.now()}`;\n if (config.provider === 'browserbase' && stagehand.browserbaseSessionID) {\n sessionId = stagehand.browserbaseSessionID;\n }\n\n return {\n stagehand,\n page,\n sessionId,\n close: async () => {\n await stagehand.close().catch(() => {});\n },\n };\n}\n\n/**\n * Suppress the BugBear widget in the browser session.\n *\n * Uses Playwright's addInitScript() on the browser context to set a suppression\n * flag before any page script runs. This prevents the widget from rendering and\n * interfering with test execution (clicking the widget instead of app UI, popups\n * covering elements, etc.).\n *\n * The flag is checked by BugBearProvider and BugBearPanel in @bbearai/react.\n */\nexport async function suppressBugBearWidget(stagehand: Stagehand): Promise<void> {\n try {\n const ctx = stagehand.context as any;\n if (ctx?.addInitScript) {\n await ctx.addInitScript(() => {\n (window as any).__bugbear_suppress = true;\n try { localStorage.setItem('__bugbear_suppress', 'true'); } catch {}\n });\n }\n } catch {\n // addInitScript not available — fallback to page.evaluate in runner\n }\n}\n\n/**\n * Inject authentication into the browser session.\n * Uses Stagehand's Page API and CDP for cookie injection.\n */\nexport async function injectAuth(page: Page, auth: AuthConfig, stagehand?: Stagehand): Promise<void> {\n if (auth.type === 'cookie') {\n // Use CDP to set cookies (Stagehand's Page doesn't have context().addCookies)\n for (const c of auth.cookies) {\n await page.sendCDP('Network.setCookie', {\n name: c.name,\n value: c.value,\n domain: c.domain,\n path: c.path ?? '/',\n secure: c.secure ?? false,\n httpOnly: c.httpOnly ?? false,\n sameSite: c.sameSite ?? 'Lax',\n });\n }\n } else if (auth.type === 'localStorage') {\n const currentUrl = page.url();\n if (currentUrl === 'about:blank') {\n // We'll inject localStorage after navigation in the runner\n return;\n }\n await page.evaluate((items: Record<string, string>) => {\n for (const [key, value] of Object.entries(items)) {\n localStorage.setItem(key, value);\n }\n }, auth.items);\n } else if (auth.type === 'form-login') {\n await performFormLogin(page, auth, stagehand);\n } else if (auth.type === 'supabase-native') {\n await performSupabaseAuth(page, auth);\n }\n}\n\n// ============================================\n// Network Capture (for exploratory testing)\n// ============================================\n\nexport interface NetworkCapture {\n start: () => void;\n stop: () => void;\n getRequests: () => CapturedRequest[];\n getErrors: () => NetworkError[];\n}\n\n/**\n * Create a per-action network capture for a Stagehand Page.\n *\n * Captures all non-static network requests and categorises errors (4xx/5xx\n * responses and failed requests). The caller controls the capture window\n * via `start()` / `stop()` — typically one window per exploration action.\n *\n * NOTE: Stagehand's Page type doesn't expose Playwright's `on('response')`\n * in its type definitions, but the underlying Playwright page supports it.\n * We cast to `any` for the event-listener calls (same pattern as runner.ts).\n */\nexport function createNetworkCapture(page: Page): NetworkCapture {\n const requests: CapturedRequest[] = [];\n const errors: NetworkError[] = [];\n let active = false;\n let startTimestamp = Date.now();\n\n const onResponse = async (response: any) => {\n if (!active) return;\n const req = response.request();\n // Skip static assets\n const resourceType = typeof req.resourceType === 'function' ? req.resourceType() : req.resourceType;\n if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) return;\n\n const entry: CapturedRequest = {\n method: typeof req.method === 'function' ? req.method() : String(req.method),\n url: (typeof response.url === 'function' ? response.url() : String(response.url)).slice(0, 500),\n status: typeof response.status === 'function' ? response.status() : Number(response.status),\n timestamp: new Date().toISOString(),\n };\n\n // Capture response body for non-2xx API responses (truncated)\n const status = entry.status;\n if (status >= 400) {\n try {\n const body = await response.text();\n entry.responseBody = body.slice(0, 500);\n } catch {}\n errors.push({\n method: entry.method,\n url: entry.url,\n status,\n statusText: typeof response.statusText === 'function' ? response.statusText() : String(response.statusText ?? ''),\n timestamp: Date.now() - startTimestamp,\n });\n }\n\n // Capture request body for POST/PUT/PATCH\n if (['POST', 'PUT', 'PATCH'].includes(entry.method)) {\n try {\n const postData = typeof req.postData === 'function' ? req.postData() : req.postData;\n if (postData) entry.requestBody = String(postData).slice(0, 500);\n } catch {}\n }\n\n requests.push(entry);\n };\n\n const onRequestFailed = (req: any) => {\n if (!active) return;\n const url = typeof req.url === 'function' ? req.url() : String(req.url ?? '');\n const method = typeof req.method === 'function' ? req.method() : String(req.method ?? 'GET');\n const failure = typeof req.failure === 'function' ? req.failure() : req.failure;\n errors.push({\n method,\n url: url.slice(0, 500),\n status: 0,\n statusText: failure?.errorText ?? 'Request failed',\n timestamp: Date.now() - startTimestamp,\n });\n };\n\n const rawPage = page as any;\n\n let responseSupported = true;\n let requestFailedSupported = true;\n\n return {\n start() {\n active = true;\n requests.length = 0;\n errors.length = 0;\n startTimestamp = Date.now();\n // Stagehand may not support these Playwright events — degrade gracefully\n if (responseSupported) {\n try { rawPage.on('response', onResponse); } catch { responseSupported = false; }\n }\n if (requestFailedSupported) {\n try { rawPage.on('requestfailed', onRequestFailed); } catch { requestFailedSupported = false; }\n }\n },\n stop() {\n active = false;\n if (responseSupported) {\n try { rawPage.off('response', onResponse); } catch {}\n }\n if (requestFailedSupported) {\n try { rawPage.off('requestfailed', onRequestFailed); } catch {}\n }\n },\n getRequests: () => [...requests],\n getErrors: () => [...errors],\n };\n}\n\n/**\n * Automate login via a web form.\n *\n * Uses Stagehand's act() when available for AI-powered form filling,\n * falling back to manual locator-based login.\n */\nasync function performFormLogin(\n page: Page,\n auth: { loginUrl: string; email: string; password: string },\n stagehand?: Stagehand,\n): Promise<void> {\n await page.goto(auth.loginUrl, { waitUntil: 'domcontentloaded' });\n\n // Wait for the page to settle\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n\n // Always fill credentials via direct DOM manipulation — never send them through AI prompts\n await fillLoginCredentials(page, auth);\n\n if (stagehand) {\n // Use Stagehand AI only for the submit button click (no credentials in the prompt)\n await stagehand.act(\n 'Click the login, sign-in, or submit button to submit the form.',\n ).catch(() => {\n // Stagehand couldn't find submit — fall back to manual submit\n });\n } else {\n await clickSubmitButton(page);\n }\n\n // Wait for navigation after login\n await page.waitForLoadState('networkidle', 15_000).catch(() => {});\n}\n\n/**\n * Fill login credentials using direct DOM manipulation.\n * Credentials stay local — never sent through AI prompts.\n */\nasync function fillLoginCredentials(\n page: Page,\n auth: { email: string; password: string },\n): Promise<void> {\n // Wait for a form or input to appear\n await page\n .waitForSelector(\n 'input[type=\"email\"], input[type=\"text\"][name*=\"email\"], input[name*=\"user\"], input[type=\"text\"]',\n { timeout: 15_000 },\n )\n .catch(() => {});\n\n // Try common email/username selectors in priority order\n const emailSelectors = [\n 'input[type=\"email\"]',\n 'input[name=\"email\"]',\n 'input[name=\"username\"]',\n 'input[autocomplete=\"email\"]',\n 'input[autocomplete=\"username\"]',\n 'input[type=\"text\"][name*=\"email\"]',\n 'input[type=\"text\"][name*=\"user\"]',\n 'input[type=\"text\"]',\n ];\n\n let emailFilled = false;\n for (const sel of emailSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.fill(auth.email);\n emailFilled = true;\n break;\n }\n }\n\n if (!emailFilled) {\n throw new Error('Could not find email/username input on login page');\n }\n\n // Fill password\n const passwordLocator = page.locator('input[type=\"password\"]');\n if ((await passwordLocator.count()) > 0 && (await passwordLocator.isVisible())) {\n await passwordLocator.fill(auth.password);\n } else {\n throw new Error('Could not find password input on login page');\n }\n}\n\n/**\n * Click the submit button using locator-based fallbacks.\n */\nasync function clickSubmitButton(page: Page): Promise<void> {\n const submitSelectors = [\n 'button[type=\"submit\"]',\n 'input[type=\"submit\"]',\n ];\n\n let submitted = false;\n for (const sel of submitSelectors) {\n const locator = page.locator(sel);\n if ((await locator.count()) > 0 && (await locator.isVisible())) {\n await locator.click();\n submitted = true;\n break;\n }\n }\n\n if (!submitted) {\n // Fallback: press Enter on the password field\n await page.locator('input[type=\"password\"]').type('\\n');\n }\n}\n","/**\n * Supabase Native Auth\n *\n * Authenticates against Supabase GoTrue API directly, bypassing fragile\n * form-based login. Injects the session into localStorage so the app\n * picks it up on page load — no DOM interaction required.\n */\n\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { SupabaseNativeAuth } from './types';\n\n/** Supabase GoTrue session shape (subset we need) */\ninterface GoTrueSession {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n expires_at: number;\n token_type: string;\n user: {\n id: string;\n email: string;\n role: string;\n aud: string;\n };\n}\n\n/**\n * Extract the project ref from a Supabase URL.\n * e.g. \"https://abcdef.supabase.co\" → \"abcdef\"\n */\nfunction extractProjectRef(supabaseUrl: string): string {\n const url = new URL(supabaseUrl);\n const hostname = url.hostname;\n const ref = hostname.split('.')[0];\n return ref;\n}\n\n/**\n * Authenticate via Supabase GoTrue REST API and return the session.\n */\nexport async function authenticateSupabase(\n auth: SupabaseNativeAuth,\n): Promise<GoTrueSession> {\n const url = `${auth.supabaseUrl.replace(/\\/$/, '')}/auth/v1/token?grant_type=password`;\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'apikey': auth.anonKey,\n },\n body: JSON.stringify({\n email: auth.email,\n password: auth.password,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text().catch(() => '');\n throw new Error(\n `Supabase auth failed (${response.status}): ${body.slice(0, 200)}`,\n );\n }\n\n const session: GoTrueSession = await response.json();\n\n if (!session.access_token) {\n throw new Error('Supabase auth returned no access_token');\n }\n\n return session;\n}\n\n/**\n * Inject a Supabase session into the browser's localStorage.\n *\n * The app's Supabase client reads from `sb-<ref>-auth-token` on load.\n * We inject the token into localStorage so the app authenticates on\n * the next page load — no DOM interaction needed.\n */\nexport async function injectSupabaseAuth(\n page: Page,\n auth: SupabaseNativeAuth,\n session: GoTrueSession,\n): Promise<void> {\n const ref = extractProjectRef(auth.supabaseUrl);\n const storageKey = `sb-${ref}-auth-token`;\n\n const storageValue = JSON.stringify({\n access_token: session.access_token,\n refresh_token: session.refresh_token,\n expires_in: session.expires_in,\n expires_at: session.expires_at,\n token_type: session.token_type,\n user: session.user,\n });\n\n // Need an origin for localStorage — if on about:blank, navigate first\n const currentUrl = page.url();\n if (currentUrl === 'about:blank' || !currentUrl) {\n await page.goto(auth.supabaseUrl.replace(/\\/$/, ''), {\n waitUntil: 'domcontentloaded',\n timeoutMs: 10_000,\n }).catch(() => {\n // Supabase URL itself may not serve HTML — we just need an origin\n });\n }\n\n await page.evaluate(\n ({ key, value }: { key: string; value: string }) => {\n localStorage.setItem(key, value);\n },\n { key: storageKey, value: storageValue },\n );\n}\n\n/**\n * Verify the session is valid by calling the Supabase user endpoint.\n */\nexport async function verifySupabaseSession(\n auth: SupabaseNativeAuth,\n accessToken: string,\n): Promise<boolean> {\n const url = `${auth.supabaseUrl.replace(/\\/$/, '')}/auth/v1/user`;\n\n const response = await fetch(url, {\n headers: {\n 'Authorization': `Bearer ${accessToken}`,\n 'apikey': auth.anonKey,\n },\n });\n\n return response.ok;\n}\n\n/**\n * Full Supabase auth flow: authenticate → inject → verify.\n */\nexport async function performSupabaseAuth(\n page: Page,\n auth: SupabaseNativeAuth,\n): Promise<void> {\n const session = await authenticateSupabase(auth);\n await injectSupabaseAuth(page, auth, session);\n\n const valid = await verifySupabaseSession(auth, session.access_token);\n if (!valid) {\n throw new Error('Supabase auth verification failed — session token rejected');\n }\n}\n","/**\n * Result Evaluator\n *\n * Generates AI summaries of test run results.\n * Step-level evaluation is handled by vision-evaluator.ts (Claude Vision).\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\n/**\n * Generate an AI summary of the entire test run.\n */\nexport async function generateRunSummary(\n anthropic: Anthropic,\n testTitle: string,\n steps: Array<{\n stepNumber: number;\n action: string;\n expectedResult: string;\n actualResult: string;\n passed: boolean;\n confidence: number;\n error?: string;\n skipped?: boolean;\n }>,\n model: string,\n): Promise<string> {\n const stepsText = steps\n .map(\n (s) =>\n `Step ${s.stepNumber}: ${s.action}\\n Expected: ${s.expectedResult}\\n Actual: ${s.actualResult}\\n Result: ${s.skipped ? 'SKIPPED' : s.passed ? 'PASS' : 'FAIL'} (confidence: ${Math.round(s.confidence * 100)}%)${s.error ? `\\n Error: ${s.error}` : ''}`\n )\n .join('\\n\\n');\n\n const passCount = steps.filter((s) => s.passed && !s.skipped).length;\n const failCount = steps.filter((s) => !s.passed && !s.skipped).length;\n const skipCount = steps.filter((s) => s.skipped).length;\n\n const skipNote = skipCount > 0\n ? ' Some steps were skipped due to page state recovery — these are not failures, just steps that could not be executed.'\n : '';\n\n const response = await anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: `Summarize this AI test execution in 2-3 sentences. Focus on what was tested, what passed, and what failed (if anything).${skipNote} Be concise and factual.\n\nTest: ${testTitle}\nResults: ${passCount} passed, ${failCount} failed, ${skipCount} skipped out of ${steps.length} steps\n\n${stepsText}`,\n },\n ],\n });\n\n return response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n}\n","/**\n * Vision-Based Step Evaluator\n *\n * Replaces Stagehand's extract() with direct Claude Messages API calls\n * using before/after screenshots. This gives Claude visual context\n * instead of just DOM text, catching visual regressions, layout shifts,\n * and rendering issues that DOM-only evaluation misses.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\nexport interface StepEvaluationInput {\n anthropic: Anthropic;\n screenshotBefore: Buffer;\n screenshotAfter: Buffer;\n action: string;\n expectedResult: string;\n /** Optional hint to guide what the evaluator should look for */\n evaluationHint?: string;\n model?: string;\n}\n\nexport interface StepEvaluation {\n passed: boolean;\n confidence: number;\n actualResult: string;\n}\n\n/**\n * Evaluate a test step by comparing before/after screenshots using Claude Vision.\n *\n * Sends both screenshots as image content blocks along with a structured\n * evaluation prompt. Returns a typed assessment with pass/fail, confidence,\n * and a description of what actually happened.\n */\nexport async function evaluateStep(input: StepEvaluationInput): Promise<StepEvaluation> {\n const model = input.model ?? DEFAULT_MODEL;\n\n const hintClause = input.evaluationHint\n ? `\\nEVALUATION HINT: ${input.evaluationHint}`\n : '';\n\n const response = await input.anthropic.messages.create({\n model,\n max_tokens: 512,\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'text',\n text: 'BEFORE screenshot (page state before the action):',\n },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: input.screenshotBefore.toString('base64'),\n },\n },\n {\n type: 'text',\n text: 'AFTER screenshot (page state after the action):',\n },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: input.screenshotAfter.toString('base64'),\n },\n },\n {\n type: 'text',\n text: `You are a QA test evaluator. Compare the BEFORE and AFTER screenshots to evaluate this test step.\n\nACTION PERFORMED: ${input.action}\nEXPECTED RESULT: ${input.expectedResult}${hintClause}\n\nAnalyze the visual differences between the two screenshots and determine if the expected result was achieved.\n\nRespond with ONLY a JSON object (no markdown, no explanation outside the JSON):\n{\n \"passed\": true/false,\n \"confidence\": 0.0-1.0,\n \"actualResult\": \"Brief description of what actually changed between the screenshots\"\n}\n\nConfidence guide:\n- 0.95-1.0: Clearly achieved/not achieved, obvious visual evidence\n- 0.8-0.94: Very likely, strong visual indicators\n- 0.6-0.79: Probable but some ambiguity\n- Below 0.6: Uncertain, hard to tell from screenshots alone`,\n },\n ],\n },\n ],\n });\n\n const text = response.content\n .filter((block): block is Anthropic.TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('');\n\n return parseEvaluation(text);\n}\n\n/**\n * Parse the evaluation response from Claude.\n * Handles JSON extraction from potentially wrapped responses.\n */\nfunction parseEvaluation(text: string): StepEvaluation {\n // Try direct JSON parse first\n try {\n const parsed = JSON.parse(text.trim());\n return validateEvaluation(parsed);\n } catch {\n // Try to extract JSON from markdown code blocks or surrounding text\n const jsonMatch = text.match(/\\{[\\s\\S]*\"passed\"[\\s\\S]*\"confidence\"[\\s\\S]*\"actualResult\"[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n return validateEvaluation(parsed);\n } catch {\n // Fall through to default\n }\n }\n }\n\n // Fallback: couldn't parse response\n return {\n passed: false,\n confidence: 0.3,\n actualResult: `Vision evaluation returned unparseable response: ${text.slice(0, 200)}`,\n };\n}\n\n/**\n * Validate and normalize the parsed evaluation object.\n */\nfunction validateEvaluation(parsed: Record<string, unknown>): StepEvaluation {\n return {\n passed: typeof parsed.passed === 'boolean' ? parsed.passed : false,\n confidence: typeof parsed.confidence === 'number'\n ? Math.max(0, Math.min(1, parsed.confidence))\n : 0.5,\n actualResult: typeof parsed.actualResult === 'string'\n ? parsed.actualResult\n : 'No description provided',\n };\n}\n","/**\n * Action Executor\n *\n * Executes test step actions using a tiered approach:\n * 1. If step has selector + actionType → Playwright direct (deterministic, fast)\n * 2. If step has only natural language → Stagehand AI fallback\n * 3. If Playwright action fails → Fall back to Stagehand with the natural language\n *\n * This eliminates AI flakiness for steps that have been enriched with\n * selectors while preserving AI flexibility for natural-language-only steps.\n */\n\nimport type { Stagehand, Page } from '@browserbasehq/stagehand';\nimport type { TestStep } from './types';\n\nexport interface ActionResult {\n /** Whether the action was executed via Playwright (true) or Stagehand AI (false) */\n deterministic: boolean;\n /** Error message if action failed */\n error?: string;\n}\n\n/**\n * Execute a test step action, preferring deterministic Playwright\n * when the step has a selector, falling back to Stagehand AI otherwise.\n */\nexport async function executeAction(\n page: Page,\n stagehand: Stagehand,\n step: TestStep,\n): Promise<ActionResult> {\n // Path 1: Deterministic Playwright execution\n if (step.selector && step.actionType) {\n try {\n await executePlaywrightAction(page, step);\n return { deterministic: true };\n } catch (err) {\n // Playwright failed — fall back to Stagehand AI with natural language\n const fallbackResult = await executeStagehandAction(stagehand, step);\n return {\n deterministic: false,\n error: fallbackResult.error\n ? `Playwright failed (${err instanceof Error ? err.message : String(err)}), Stagehand fallback also failed: ${fallbackResult.error}`\n : undefined,\n };\n }\n }\n\n // Path 2: Stagehand AI (natural language only)\n return executeStagehandAction(stagehand, step);\n}\n\n/**\n * Execute a step using Playwright's locator API directly.\n * No AI involved — deterministic and fast.\n */\nasync function executePlaywrightAction(page: Page, step: TestStep): Promise<void> {\n const { actionType, selector, value, waitMs } = step;\n\n // Stagehand's Page/Locator API is a subset of Playwright's —\n // methods like click(), fill() don't accept option objects.\n switch (actionType) {\n case 'click': {\n const locator = page.locator(selector!);\n await locator.click();\n break;\n }\n case 'fill': {\n const locator = page.locator(selector!);\n await locator.fill(value ?? '');\n break;\n }\n case 'select': {\n // Stagehand's locator doesn't expose selectOption — use evaluate\n await page.evaluate(\n ({ sel, val }: { sel: string; val: string }) => {\n const el = document.querySelector(sel) as HTMLSelectElement | null;\n if (!el) throw new Error(`Select element not found: ${sel}`);\n el.value = val;\n el.dispatchEvent(new Event('change', { bubbles: true }));\n },\n { sel: selector!, val: value ?? '' },\n );\n break;\n }\n case 'navigate': {\n const url = value ?? selector ?? '';\n if (!url) throw new Error('Navigate action requires a value or selector with the URL');\n await page.goto(url, { waitUntil: 'domcontentloaded', timeoutMs: 15_000 });\n break;\n }\n case 'scroll': {\n // Use evaluate for scroll — Stagehand's Locator doesn't expose scrollIntoViewIfNeeded\n await page.evaluate((sel: string) => {\n const el = document.querySelector(sel);\n if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }, selector!);\n break;\n }\n case 'wait': {\n if (selector) {\n await page.waitForSelector(selector, { timeout: waitMs ?? 10_000 });\n } else if (waitMs) {\n await page.waitForTimeout(waitMs);\n }\n break;\n }\n case 'assert': {\n // Assert steps don't perform browser actions — evaluation is handled\n // by the vision evaluator after the screenshot comparison.\n break;\n }\n default: {\n throw new Error(`Unknown actionType: ${actionType}`);\n }\n }\n\n // Apply explicit post-action wait if specified\n if (waitMs && actionType !== 'wait') {\n await page.waitForTimeout(waitMs);\n }\n}\n\n/**\n * Execute a step using Stagehand's AI-powered act().\n * Used as the primary path for unenriched steps and as a fallback.\n */\nasync function executeStagehandAction(\n stagehand: Stagehand,\n step: TestStep,\n): Promise<ActionResult> {\n try {\n await stagehand.act(step.action);\n return { deterministic: false };\n } catch (err) {\n return {\n deterministic: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","/**\n * Selector Discovery\n *\n * After Stagehand successfully executes a natural-language step,\n * attempts to discover which element was interacted with. Records\n * the best available selector so the test case can be enriched\n * for deterministic execution next time.\n *\n * Discovery data is stored in ai_step_results.actions_taken (JSONB).\n */\n\nimport type { Page } from '@browserbasehq/stagehand';\nimport type { StepActionType } from './types';\n\nexport interface DiscoveredSelector {\n /** The selector that was discovered */\n selector: string;\n /** How the selector was derived */\n strategy: 'data-testid' | 'role' | 'aria-label' | 'id' | 'css-path';\n /** Suggested actionType based on the element */\n suggestedActionType?: StepActionType;\n /** Element tag name */\n tagName: string;\n /** Visible text content (truncated) */\n textContent?: string;\n}\n\n/**\n * Attempt to discover the selector for the last-interacted element.\n *\n * Uses page.evaluate() to find the currently focused or last-clicked\n * element and extract the best available selector for it.\n *\n * Returns null if no element can be identified.\n */\nexport async function discoverSelector(page: Page): Promise<DiscoveredSelector | null> {\n try {\n const result = await page.evaluate(() => {\n // Try to find the last-interacted element\n // Priority: activeElement (focused), then last-clicked via __bbLastClicked\n const el = (document as any).__bbLastClicked ?? document.activeElement;\n if (!el || el === document.body || el === document.documentElement) return null;\n\n const tagName = el.tagName?.toLowerCase() ?? 'unknown';\n const textContent = (el.textContent ?? '').trim().slice(0, 100);\n\n // Build selector by priority\n let selector = '';\n let strategy = 'css-path';\n\n // 1. data-testid (most stable)\n const testId = el.getAttribute('data-testid') ?? el.getAttribute('data-test-id');\n if (testId) {\n selector = `[data-testid=\"${testId}\"]`;\n strategy = 'data-testid';\n }\n // 2. id (stable if not auto-generated)\n else if (el.id && !/^:r[0-9a-z]+:?$/.test(el.id) && !/^react-/.test(el.id)) {\n selector = `#${el.id}`;\n strategy = 'id';\n }\n // 3. role + name (accessible)\n else if (el.getAttribute('role')) {\n const role = el.getAttribute('role');\n const name = el.getAttribute('aria-label') ?? el.getAttribute('name') ?? '';\n if (name) {\n selector = `[role=\"${role}\"][aria-label=\"${name}\"]`;\n strategy = 'role';\n } else {\n selector = `[role=\"${role}\"]`;\n strategy = 'role';\n }\n }\n // 4. aria-label\n else if (el.getAttribute('aria-label')) {\n selector = `[aria-label=\"${el.getAttribute('aria-label')}\"]`;\n strategy = 'aria-label';\n }\n // 5. CSS path (least stable, last resort)\n else {\n const parts: string[] = [];\n let current = el;\n while (current && current !== document.body) {\n let part = current.tagName.toLowerCase();\n if (current.className && typeof current.className === 'string') {\n // Use first non-utility class\n const classes = current.className.split(/\\s+/).filter(\n (c: string) => c && !c.startsWith('_') && c.length < 30\n );\n if (classes.length > 0) {\n part += `.${classes[0]}`;\n }\n }\n parts.unshift(part);\n current = current.parentElement;\n if (parts.length >= 4) break; // Don't go too deep\n }\n selector = parts.join(' > ');\n strategy = 'css-path';\n }\n\n // Suggest actionType based on element\n let suggestedActionType: string | undefined;\n if (tagName === 'button' || tagName === 'a' || el.getAttribute('role') === 'button') {\n suggestedActionType = 'click';\n } else if (tagName === 'input' || tagName === 'textarea') {\n const type = el.getAttribute('type') ?? 'text';\n if (type === 'checkbox' || type === 'radio') {\n suggestedActionType = 'click';\n } else {\n suggestedActionType = 'fill';\n }\n } else if (tagName === 'select') {\n suggestedActionType = 'select';\n }\n\n return { selector, strategy, suggestedActionType, tagName, textContent };\n });\n\n return result as DiscoveredSelector | null;\n } catch {\n // Discovery is best-effort — don't fail the test\n return null;\n }\n}\n\n/**\n * Install a click tracker on the page.\n *\n * Records the last-clicked element in `document.__bbLastClicked`\n * so discoverSelector() can find it after Stagehand's act().\n * Should be called once after page navigation.\n */\nexport async function installClickTracker(page: Page): Promise<void> {\n try {\n await page.evaluate(() => {\n document.addEventListener('click', (e) => {\n (document as any).__bbLastClicked = e.target;\n }, { capture: true });\n });\n } catch {\n // Best-effort — Stagehand may not support this\n }\n}\n","/**\n * AI Test Execution Cost Estimation\n *\n * Provides pre-run cost estimates and post-run cost calculations\n * based on per-model token pricing and calibrated usage profiles.\n */\n\n// Per-1M token pricing (USD) — updated Feb 2026\nconst MODEL_PRICING: Record<string, { input: number; output: number }> = {\n 'claude-sonnet-4-20250514': { input: 3.0, output: 15.0 },\n 'claude-haiku-4-20250514': { input: 0.80, output: 4.0 },\n 'claude-opus-4-20250514': { input: 15.0, output: 75.0 },\n // Aliases\n 'sonnet': { input: 3.0, output: 15.0 },\n 'haiku': { input: 0.80, output: 4.0 },\n 'opus': { input: 15.0, output: 75.0 },\n};\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-20250514';\n\n// Calibrated token usage per operation (from real Stagehand runs)\nconst TOKEN_PROFILE = {\n /** act() — screenshot + DOM context → action decision */\n actInput: 2000,\n actOutput: 200,\n /** extract() — screenshot + extraction schema → structured result */\n extractInput: 3000,\n extractOutput: 500,\n /** summary — all step results → narrative summary (once per run) */\n summaryInput: 2000,\n summaryOutput: 500,\n};\n\nexport interface CostEstimate {\n /** Cost in cents (USD) */\n cents: number;\n /** Formatted string (e.g., \"$0.12\") */\n formatted: string;\n /** Token breakdown */\n tokens: {\n inputTokens: number;\n outputTokens: number;\n };\n /** Model used for estimate */\n model: string;\n}\n\n/**\n * Calculate actual cost from known token counts.\n */\nexport function estimateCost(\n inputTokens: number,\n outputTokens: number,\n model?: string,\n): CostEstimate {\n const resolvedModel = model ?? DEFAULT_MODEL;\n const pricing = MODEL_PRICING[resolvedModel] ?? MODEL_PRICING[DEFAULT_MODEL];\n\n const inputCost = (inputTokens / 1_000_000) * pricing.input;\n const outputCost = (outputTokens / 1_000_000) * pricing.output;\n const totalDollars = inputCost + outputCost;\n const cents = Math.round(totalDollars * 100 * 100) / 100; // 2 decimal cents\n\n return {\n cents,\n formatted: `$${totalDollars.toFixed(4)}`,\n tokens: { inputTokens, outputTokens },\n model: resolvedModel,\n };\n}\n\n/**\n * Pre-run cost estimate based on step count.\n * Each step involves: act() + extract(). Plus one summary at the end.\n */\nexport function estimateTestCost(stepCount: number, model?: string): CostEstimate {\n const inputTokens =\n stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput;\n const outputTokens =\n stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput;\n\n return estimateCost(inputTokens, outputTokens, model);\n}\n\n/**\n * Estimate cost for a batch of test cases.\n */\nexport function estimateBatchCost(\n testCases: Array<{ stepCount: number }>,\n model?: string,\n): CostEstimate {\n let totalInput = 0;\n let totalOutput = 0;\n\n for (const tc of testCases) {\n totalInput +=\n tc.stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput;\n totalOutput +=\n tc.stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput;\n }\n\n return estimateCost(totalInput, totalOutput, model);\n}\n\n/**\n * Get calibrated token estimates for a test with N steps.\n * More accurate than the old hardcoded 3000/500 per step.\n */\nexport function getTokenEstimate(stepCount: number): {\n inputTokens: number;\n outputTokens: number;\n} {\n return {\n inputTokens:\n stepCount * (TOKEN_PROFILE.actInput + TOKEN_PROFILE.extractInput) +\n TOKEN_PROFILE.summaryInput,\n outputTokens:\n stepCount * (TOKEN_PROFILE.actOutput + TOKEN_PROFILE.extractOutput) +\n TOKEN_PROFILE.summaryOutput,\n };\n}\n"],"mappings":"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","names":["DEFAULT_MODEL","Anthropic"]}