assuremind 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/config.ts","../src/storage/suite-store.ts","../src/types/suite.ts","../src/utils/errors.ts","../src/utils/logger.ts","../src/utils/sanitize.ts","../src/storage/utils.ts","../src/storage/case-store.ts","../src/storage/variable-store.ts","../src/types/variable.ts","../src/storage/config-store.ts","../src/storage/result-store.ts","../src/types/run.ts","../src/storage/healing-store.ts","../src/types/healing.ts","../src/utils/hash.ts","../src/utils/env.ts","../src/index.ts"],"sourcesContent":["import { z } from 'zod';\r\n\r\nexport const ScreenshotModeSchema = z.enum(['off', 'on', 'only-on-failure']);\r\nexport const VideoModeSchema = z.enum(['off', 'on', 'on-first-retry', 'retain-on-failure']);\r\nexport const TraceModeSchema = z.enum(['off', 'on', 'on-first-retry', 'retain-on-failure']);\r\nexport const BrowserNameSchema = z.enum(['chromium', 'firefox', 'webkit']);\r\n\r\n/**\r\n * Page-load wait strategy applied after every navigation / action.\r\n * commit — wait only until the first network response (fastest)\r\n * domcontentloaded — wait until DOMContentLoaded fires (recommended default)\r\n * load — wait until the load event fires\r\n * networkidle — wait until no network activity for 500 ms (slowest, most stable)\r\n */\r\nexport const PageLoadStrategySchema = z.enum(['commit', 'domcontentloaded', 'load', 'networkidle']);\r\nexport const EnvironmentSchema = z.enum(['dev', 'stage', 'test', 'prod']);\r\nexport type Environment = z.infer<typeof EnvironmentSchema>;\r\n\r\nexport const EnvironmentUrlsSchema = z.object({\r\n dev: z.string().url().or(z.literal('')).default(''),\r\n stage: z.string().url().or(z.literal('')).default(''),\r\n test: z.string().url().or(z.literal('')).default(''),\r\n prod: z.string().url().or(z.literal('')).default(''),\r\n});\r\nexport type EnvironmentUrls = z.infer<typeof EnvironmentUrlsSchema>;\r\n\r\nexport type ScreenshotMode = z.infer<typeof ScreenshotModeSchema>;\r\nexport type VideoMode = z.infer<typeof VideoModeSchema>;\r\nexport type TraceMode = z.infer<typeof TraceModeSchema>;\r\nexport type BrowserName = z.infer<typeof BrowserNameSchema>;\r\nexport type PageLoadStrategy = z.infer<typeof PageLoadStrategySchema>;\r\n\r\nexport const HealingConfigSchema = z.object({\r\n enabled: z.boolean(),\r\n maxLevel: z.number().int().min(1).max(6),\r\n dailyBudget: z.number().positive(),\r\n autoPR: z.boolean(),\r\n});\r\n\r\nexport const ReportingConfigSchema = z.object({\r\n allure: z.boolean(),\r\n html: z.boolean(),\r\n json: z.boolean(),\r\n});\r\n\r\nexport const ViewportSchema = z.object({\r\n width: z.number().int().positive(),\r\n height: z.number().int().positive(),\r\n});\r\nexport type Viewport = z.infer<typeof ViewportSchema>;\r\n\r\nexport const EnvironmentProfileSchema = z.object({\r\n name: z.string().min(1),\r\n environment: EnvironmentSchema,\r\n baseUrl: z.string().url(),\r\n browsers: z.array(BrowserNameSchema).min(1),\r\n headless: z.boolean().optional(),\r\n});\r\n\r\nexport type EnvironmentProfile = z.infer<typeof EnvironmentProfileSchema>;\r\n\r\nexport const AutotestConfigSchema = z.object({\r\n baseUrl: z.string().url(),\r\n environment: EnvironmentSchema.default('stage'),\r\n environmentUrls: EnvironmentUrlsSchema.default({\r\n dev: '',\r\n stage: '',\r\n test: '',\r\n prod: '',\r\n }),\r\n browsers: z.array(BrowserNameSchema).min(1),\r\n headless: z.boolean(),\r\n viewport: ViewportSchema.default({ width: 1280, height: 720 }),\r\n timeout: z.number().int().positive(),\r\n retries: z.number().int().min(0),\r\n parallel: z.number().int().positive(),\r\n pageLoad: PageLoadStrategySchema.default('domcontentloaded'),\r\n screenshot: ScreenshotModeSchema,\r\n video: VideoModeSchema,\r\n trace: TraceModeSchema,\r\n healing: HealingConfigSchema,\r\n reporting: ReportingConfigSchema,\r\n studioPort: z.number().int().min(1024).max(65535),\r\n profiles: z.array(EnvironmentProfileSchema).default([]),\r\n activeProfile: z.string().optional(),\r\n /** Playwright device descriptor name for emulation (e.g. 'iPhone 15 Pro'). */\r\n device: z.string().optional(),\r\n});\r\n\r\nexport type HealingConfig = z.infer<typeof HealingConfigSchema>;\r\nexport type ReportingConfig = z.infer<typeof ReportingConfigSchema>;\r\nexport type AutotestConfig = z.infer<typeof AutotestConfigSchema>;\r\n\r\nexport const DEFAULT_CONFIG: AutotestConfig = {\r\n baseUrl: 'http://localhost:3000',\r\n environment: 'stage',\r\n environmentUrls: {\r\n dev: '',\r\n stage: 'http://localhost:3000',\r\n test: '',\r\n prod: '',\r\n },\r\n browsers: ['chromium'],\r\n headless: true,\r\n viewport: { width: 1280, height: 720 },\r\n timeout: 30000,\r\n retries: 1,\r\n parallel: 1,\r\n pageLoad: 'domcontentloaded',\r\n screenshot: 'only-on-failure',\r\n video: 'off',\r\n trace: 'on-first-retry',\r\n healing: {\r\n enabled: true,\r\n maxLevel: 5,\r\n dailyBudget: 5.0,\r\n autoPR: false,\r\n },\r\n reporting: {\r\n allure: true,\r\n html: true,\r\n json: true,\r\n },\r\n studioPort: 4400,\r\n profiles: [],\r\n};\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { v4 as uuidv4 } from 'uuid';\r\nimport { TestSuite, TestSuiteSchema, type SuiteType } from '../types/suite.js';\r\nimport { StorageError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { toSlug } from '../utils/sanitize.js';\r\nimport { atomicWriteJson, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('suite-store');\r\n\r\nconst SUITE_FILE = 'suite.json';\r\n\r\n/**\r\n * Reads a suite.json from the given suite directory.\r\n */\r\nexport async function readSuite(suiteDir: string): Promise<TestSuite> {\r\n const filePath = path.join(suiteDir, SUITE_FILE);\r\n\r\n if (!(await fs.pathExists(filePath))) {\r\n throw new StorageError(\r\n `Suite file not found at \"${filePath}\". ` +\r\n `Ensure the suite directory exists and contains a suite.json file.`,\r\n filePath,\r\n 'SUITE_NOT_FOUND',\r\n );\r\n }\r\n\r\n const raw = await readJson(filePath);\r\n const result = TestSuiteSchema.safeParse(raw);\r\n\r\n if (!result.success) {\r\n const issues = result.error.issues.map((i) => ` • ${i.path.join('.')}: ${i.message}`).join('\\n');\r\n throw new ValidationError(\r\n `Invalid suite.json at \"${filePath}\":\\n${issues}`,\r\n 'suite',\r\n 'INVALID_SUITE',\r\n );\r\n }\r\n\r\n // Infer type from folder path if not explicitly set in suite.json\r\n // (Zod .default('ui') always fills the value, so check the raw JSON instead)\r\n const rawObj = raw as Record<string, unknown>;\r\n if (!rawObj.type) {\r\n const parentDir = path.basename(path.dirname(suiteDir));\r\n if (parentDir === 'api') result.data.type = 'api';\r\n else if (parentDir === 'audit') result.data.type = 'audit';\r\n else if (parentDir === 'performance') result.data.type = 'audit'; // backward compat: performance dir → audit type\r\n else result.data.type = 'ui';\r\n }\r\n\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Writes a suite.json atomically to the given suite directory.\r\n */\r\nexport async function writeSuite(suiteDir: string, suite: TestSuite): Promise<void> {\r\n await fs.ensureDir(suiteDir);\r\n const filePath = path.join(suiteDir, SUITE_FILE);\r\n\r\n const result = TestSuiteSchema.safeParse(suite);\r\n if (!result.success) {\r\n const issues = result.error.issues.map((i) => ` • ${i.path.join('.')}: ${i.message}`).join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid suite to \"${filePath}\":\\n${issues}`,\r\n 'suite',\r\n 'INVALID_SUITE',\r\n );\r\n }\r\n\r\n await atomicWriteJson(filePath, result.data);\r\n logger.debug({ suiteId: suite.id, path: filePath }, 'Suite written');\r\n}\r\n\r\n/**\r\n * Creates a new suite directory and writes its suite.json.\r\n * Returns both the directory path and the generated suite ID.\r\n */\r\nexport async function createSuite(\r\n testsDir: string,\r\n suite: Omit<TestSuite, 'id' | 'createdAt' | 'updatedAt'>,\r\n): Promise<{ suiteDir: string; suiteId: string }> {\r\n const now = new Date().toISOString();\r\n const suiteType: SuiteType = suite.type ?? 'ui';\r\n const newSuite: TestSuite = {\r\n ...suite,\r\n type: suiteType,\r\n id: uuidv4(),\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n\r\n const targetDir = path.join(testsDir, suiteType);\r\n const suiteDir = path.join(targetDir, toSlug(newSuite.name));\r\n if (await fs.pathExists(path.join(suiteDir, SUITE_FILE))) {\r\n throw new StorageError(\r\n `Suite directory already exists at \"${suiteDir}\". ` +\r\n `Choose a different name or delete the existing suite first.`,\r\n suiteDir,\r\n 'SUITE_ALREADY_EXISTS',\r\n );\r\n }\r\n\r\n await writeSuite(suiteDir, newSuite);\r\n logger.info({ suiteId: newSuite.id, path: suiteDir }, 'Suite created');\r\n return { suiteDir, suiteId: newSuite.id };\r\n}\r\n\r\n/**\r\n * Updates an existing suite (merges partial fields, updates updatedAt).\r\n */\r\nexport async function updateSuite(\r\n suiteDir: string,\r\n updates: Partial<Omit<TestSuite, 'id' | 'createdAt'>>,\r\n): Promise<TestSuite> {\r\n const existing = await readSuite(suiteDir);\r\n const updated: TestSuite = {\r\n ...existing,\r\n ...updates,\r\n id: existing.id,\r\n createdAt: existing.createdAt,\r\n updatedAt: new Date().toISOString(),\r\n };\r\n await writeSuite(suiteDir, updated);\r\n return updated;\r\n}\r\n\r\n/**\r\n * Deletes a suite directory and all its contents.\r\n */\r\nexport async function deleteSuite(suiteDir: string): Promise<void> {\r\n if (!(await fs.pathExists(suiteDir))) {\r\n throw new StorageError(\r\n `Suite directory not found at \"${suiteDir}\".`,\r\n suiteDir,\r\n 'SUITE_NOT_FOUND',\r\n );\r\n }\r\n await fs.remove(suiteDir);\r\n logger.info({ path: suiteDir }, 'Suite deleted');\r\n}\r\n\r\n/**\r\n * Lists all suite directories under testsDir.\r\n * Scans: testsDir/ui/, testsDir/api/, testsDir/performance/, and testsDir/ (legacy root).\r\n */\r\nexport async function listSuiteDirs(testsDir: string): Promise<string[]> {\r\n const suiteDirs: string[] = [];\r\n const searchDirs = [\r\n path.join(testsDir, 'ui'),\r\n path.join(testsDir, 'api'),\r\n path.join(testsDir, 'audit'),\r\n path.join(testsDir, 'performance'), // legacy: keep scanning for backward compat\r\n testsDir, // legacy: suites at root level\r\n ];\r\n\r\n for (const baseDir of searchDirs) {\r\n if (!(await fs.pathExists(baseDir))) continue;\r\n const entries = await fs.readdir(baseDir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (!entry.isDirectory()) continue;\r\n // Skip the ui/, api/, and performance/ subdirs themselves when scanning root\r\n if (baseDir === testsDir && (entry.name === 'ui' || entry.name === 'api' || entry.name === 'audit' || entry.name === 'performance')) continue;\r\n const suiteFile = path.join(baseDir, entry.name, SUITE_FILE);\r\n if (await fs.pathExists(suiteFile)) {\r\n suiteDirs.push(path.join(baseDir, entry.name));\r\n }\r\n }\r\n }\r\n\r\n return suiteDirs;\r\n}\r\n\r\n/**\r\n * Moves a suite between ui/ and api/ folders when type changes.\r\n */\r\nexport async function moveSuiteType(\r\n testsDir: string,\r\n suiteDir: string,\r\n newType: SuiteType,\r\n): Promise<string> {\r\n const suite = await readSuite(suiteDir);\r\n const suiteDirName = path.basename(suiteDir);\r\n const newParent = path.join(testsDir, newType);\r\n const newSuiteDir = path.join(newParent, suiteDirName);\r\n\r\n if (suiteDir === newSuiteDir) return suiteDir; // already in correct folder\r\n\r\n await fs.ensureDir(newParent);\r\n if (await fs.pathExists(newSuiteDir)) {\r\n throw new StorageError(\r\n `Cannot move suite — \"${newSuiteDir}\" already exists.`,\r\n newSuiteDir,\r\n 'SUITE_ALREADY_EXISTS',\r\n );\r\n }\r\n\r\n await fs.move(suiteDir, newSuiteDir);\r\n // Update type in suite.json\r\n await updateSuite(newSuiteDir, { type: newType });\r\n logger.info({ suiteId: suite.id, from: suiteDir, to: newSuiteDir }, 'Suite moved');\r\n return newSuiteDir;\r\n}\r\n\r\n/**\r\n * Finds the suite directory for a given suite UUID.\r\n * Scans all suite dirs and returns the one whose suite.json has the matching id.\r\n * Returns null if not found.\r\n */\r\nexport async function findSuiteDirById(testsDir: string, id: string): Promise<string | null> {\r\n const dirs = await listSuiteDirs(testsDir);\r\n for (const dir of dirs) {\r\n try {\r\n const suite = await readSuite(dir);\r\n if (suite.id === id) return dir;\r\n } catch {\r\n // skip malformed suites\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Reads all suites from testsDir.\r\n */\r\nexport async function listSuites(testsDir: string): Promise<TestSuite[]> {\r\n const dirs = await listSuiteDirs(testsDir);\r\n const suites: TestSuite[] = [];\r\n\r\n for (const dir of dirs) {\r\n try {\r\n suites.push(await readSuite(dir));\r\n } catch (err) {\r\n logger.warn({ path: dir, err }, 'Failed to read suite — skipping');\r\n }\r\n }\r\n\r\n return suites;\r\n}\r\n\r\n/**\r\n * Reads all suites with their case counts (lightweight — counts files, doesn't parse).\r\n */\r\nexport async function listSuitesWithCounts(testsDir: string): Promise<(TestSuite & { caseCount: number })[]> {\r\n const dirs = await listSuiteDirs(testsDir);\r\n const suites: (TestSuite & { caseCount: number })[] = [];\r\n\r\n for (const dir of dirs) {\r\n try {\r\n const suite = await readSuite(dir);\r\n const entries = await fs.readdir(dir, { withFileTypes: true });\r\n const caseCount = entries.filter((e) => e.isFile() && e.name.endsWith('.test.json')).length;\r\n suites.push({ ...suite, caseCount });\r\n } catch (err) {\r\n logger.warn({ path: dir, err }, 'Failed to read suite — skipping');\r\n }\r\n }\r\n\r\n return suites;\r\n}\r\n","import { z } from 'zod';\r\n\r\nexport type Priority = 'critical' | 'high' | 'medium' | 'low';\r\nexport type GenerationStrategy = 'template' | 'cache' | 'batch' | 'fast' | 'primary';\r\n\r\nexport const TestStepSchema = z.object({\r\n id: z.string().min(1),\r\n order: z.number().int().positive(),\r\n instruction: z.string().min(1),\r\n generatedCode: z.string(),\r\n strategy: z.enum(['template', 'cache', 'batch', 'fast', 'primary']),\r\n stepType: z.enum(['ui', 'api', 'mock']).default('ui'),\r\n lastHealed: z.string().nullable(),\r\n timeout: z.number().int().positive().optional(),\r\n retries: z.number().int().min(0).optional(),\r\n mockUrl: z.string().optional(),\r\n mockResponse: z.string().optional(),\r\n mockStatus: z.number().int().optional(),\r\n runAudit: z.boolean().optional(), // Mark this step as a Lighthouse audit checkpoint\r\n});\r\n\r\nexport const DataSourceSchema = z.object({\r\n type: z.enum(['inline', 'json-file', 'csv-file']),\r\n path: z.string().optional(),\r\n data: z.array(z.record(z.string())).optional(),\r\n}).optional();\r\n\r\nconst CaseHookStepSchema = z.object({\r\n id: z.string(),\r\n instruction: z.string(),\r\n generatedCode: z.string().default(''),\r\n order: z.number().int().default(0),\r\n});\r\n\r\nconst CaseHooksSchema = z.object({\r\n before: z.array(CaseHookStepSchema).default([]),\r\n after: z.array(CaseHookStepSchema).default([]),\r\n}).default({ before: [], after: [] });\r\n\r\nexport type CaseHookStep = z.infer<typeof CaseHookStepSchema>;\r\nexport type CaseHooks = z.infer<typeof CaseHooksSchema>;\r\n\r\nexport const TestCaseSchema = z.object({\r\n id: z.string().min(1),\r\n name: z.string().min(1),\r\n description: z.string(),\r\n tags: z.array(z.string()),\r\n priority: z.enum(['critical', 'high', 'medium', 'low']),\r\n timeout: z.number().int().positive().optional(),\r\n dataSource: DataSourceSchema,\r\n steps: z.array(TestStepSchema),\r\n caseHooks: CaseHooksSchema,\r\n lighthouseCategories: z.array(z.enum(['performance', 'accessibility', 'seo']))\r\n .default(['performance', 'accessibility', 'seo']),\r\n createdAt: z.string().datetime(),\r\n updatedAt: z.string().datetime(),\r\n});\r\n\r\nexport type SuiteType = 'ui' | 'api' | 'audit' | 'performance';\r\n\r\nexport const TestSuiteSchema = z.object({\r\n id: z.string().min(1),\r\n name: z.string().min(1),\r\n description: z.string(),\r\n tags: z.array(z.string()),\r\n type: z.enum(['ui', 'api', 'audit', 'performance']).default('ui'),\r\n timeout: z.number().int().positive().optional(),\r\n createdAt: z.string().datetime(),\r\n updatedAt: z.string().datetime(),\r\n});\r\n\r\n// ─── Suite Hooks (before_all / before_each / after_each / after_all) ─────────\r\n\r\nexport const HookTypeEnum = z.enum(['before_all', 'before_each', 'after_each', 'after_all']);\r\nexport type HookType = z.infer<typeof HookTypeEnum>;\r\n\r\nexport const SuiteHooksSchema = z.object({\r\n before_all: z.array(TestStepSchema).default([]),\r\n before_each: z.array(TestStepSchema).default([]),\r\n after_each: z.array(TestStepSchema).default([]),\r\n after_all: z.array(TestStepSchema).default([]),\r\n});\r\n\r\nexport type TestStep = z.infer<typeof TestStepSchema>;\r\nexport type TestCase = z.infer<typeof TestCaseSchema>;\r\nexport type TestSuite = z.infer<typeof TestSuiteSchema>;\r\nexport type SuiteHooks = z.infer<typeof SuiteHooksSchema>;\r\n","export class AssuremindError extends Error {\r\n public readonly code: string;\r\n\r\n constructor(message: string, code: string) {\r\n super(message);\r\n this.name = 'AssuremindError';\r\n this.code = code;\r\n Object.setPrototypeOf(this, new.target.prototype);\r\n }\r\n}\r\n\r\nexport class ProviderError extends AssuremindError {\r\n public readonly provider: string;\r\n\r\n constructor(message: string, provider: string, code = 'PROVIDER_ERROR') {\r\n super(message, code);\r\n this.name = 'ProviderError';\r\n this.provider = provider;\r\n }\r\n}\r\n\r\nexport class ExecutionError extends AssuremindError {\r\n public readonly stepId: string;\r\n\r\n constructor(message: string, stepId: string, code = 'EXECUTION_ERROR') {\r\n super(message, code);\r\n this.name = 'ExecutionError';\r\n this.stepId = stepId;\r\n }\r\n}\r\n\r\nexport class ConfigError extends AssuremindError {\r\n constructor(message: string, code = 'CONFIG_ERROR') {\r\n super(message, code);\r\n this.name = 'ConfigError';\r\n }\r\n}\r\n\r\nexport class ValidationError extends AssuremindError {\r\n public readonly field?: string;\r\n\r\n constructor(message: string, field?: string, code = 'VALIDATION_ERROR') {\r\n super(message, code);\r\n this.name = 'ValidationError';\r\n this.field = field;\r\n }\r\n}\r\n\r\nexport class HealingError extends AssuremindError {\r\n public readonly level: number;\r\n\r\n constructor(message: string, level: number, code = 'HEALING_ERROR') {\r\n super(message, code);\r\n this.name = 'HealingError';\r\n this.level = level;\r\n }\r\n}\r\n\r\nexport class StorageError extends AssuremindError {\r\n public readonly path: string;\r\n\r\n constructor(message: string, path: string, code = 'STORAGE_ERROR') {\r\n super(message, code);\r\n this.name = 'StorageError';\r\n this.path = path;\r\n }\r\n}\r\n\r\nexport function isAssuremindError(error: unknown): error is AssuremindError {\r\n return error instanceof AssuremindError;\r\n}\r\n\r\nexport function formatError(error: unknown): string {\r\n if (error instanceof AssuremindError) {\r\n return `[${error.code}] ${error.message}`;\r\n }\r\n if (error instanceof Error) {\r\n return error.message;\r\n }\r\n return String(error);\r\n}\r\n","import pino from 'pino';\r\nimport fs from 'fs-extra';\r\nimport path from 'path';\r\n\r\nconst isDevelopment = process.env['NODE_ENV'] !== 'production';\r\n\r\nconst transport = isDevelopment\r\n ? {\r\n target: 'pino-pretty',\r\n options: {\r\n colorize: true,\r\n translateTime: 'HH:MM:ss',\r\n ignore: 'pid,hostname',\r\n messageFormat: '[assuremind] {msg}',\r\n },\r\n }\r\n : undefined;\r\n\r\nexport const logger = pino(\r\n {\r\n level: process.env['LOG_LEVEL'] ?? 'info',\r\n base: { name: 'assuremind' },\r\n },\r\n transport ? pino.transport(transport) : undefined,\r\n);\r\n\r\nexport type Logger = typeof logger;\r\n\r\nexport function createChildLogger(component: string): pino.Logger {\r\n return logger.child({ component });\r\n}\r\n\r\n// ─── Per-run file logging ─────────────────────────────────────────────────────\r\n\r\n/**\r\n * Creates a per-run log file and returns a writable stream + file path.\r\n * All log entries during the run are written to this file in addition to console.\r\n */\r\nexport interface RunLogHandle {\r\n logFilePath: string;\r\n write: (entry: string) => void;\r\n close: () => void;\r\n}\r\n\r\nexport async function createRunLogFile(rootDir: string, runId: string): Promise<RunLogHandle> {\r\n const logsDir = path.join(rootDir, 'results', 'logs');\r\n await fs.ensureDir(logsDir);\r\n const logFilePath = path.join(logsDir, `run-${runId}.log`);\r\n const stream = fs.createWriteStream(logFilePath, { flags: 'a', encoding: 'utf-8' });\r\n\r\n // Write header\r\n stream.write(`=== AutoMind Test Run: ${runId} ===\\n`);\r\n stream.write(`Started: ${new Date().toISOString()}\\n`);\r\n stream.write('='.repeat(60) + '\\n\\n');\r\n\r\n return {\r\n logFilePath,\r\n write: (entry: string) => {\r\n stream.write(entry + '\\n');\r\n },\r\n close: () => {\r\n stream.write('\\n' + '='.repeat(60) + '\\n');\r\n stream.write(`Completed: ${new Date().toISOString()}\\n`);\r\n stream.end();\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Creates a pino destination that writes to both console and a run log file.\r\n * Returns a child logger that tees to the file.\r\n */\r\nexport function createRunLogger(runLogHandle: RunLogHandle): pino.Logger {\r\n const runLogger = pino(\r\n {\r\n level: process.env['LOG_LEVEL'] ?? 'info',\r\n base: { name: 'assuremind' },\r\n timestamp: pino.stdTimeFunctions.isoTime,\r\n },\r\n {\r\n write(msg: string) {\r\n // Write to the run log file (plain JSON line)\r\n runLogHandle.write(msg.trim());\r\n },\r\n },\r\n );\r\n return runLogger;\r\n}\r\n","import { ValidationError } from './errors.js';\r\n\r\n/**\r\n * Strips markdown code fences from AI-generated code responses.\r\n * Handles ```typescript, ```ts, ```javascript, ```js, and plain ``` blocks.\r\n */\r\nexport function stripCodeFences(raw: string): string {\r\n const fencePattern = /^```(?:typescript|javascript|ts|js)?\\n?([\\s\\S]*?)```\\s*$/m;\r\n const match = raw.match(fencePattern);\r\n if (match?.[1] !== undefined) {\r\n return match[1].trim();\r\n }\r\n return raw.trim();\r\n}\r\n\r\n/**\r\n * Robustly extracts the first complete JSON object `{...}` or array `[...]`\r\n * from an AI response that may have preamble text, markdown fences, or\r\n * trailing explanation. Works even when models ignore \"return only JSON\" instructions.\r\n *\r\n * Returns the extracted JSON string, or the original input if no block is found.\r\n */\r\nexport function extractJsonBlock(raw: string): string {\r\n // First try stripping code fences\r\n const stripped = stripCodeFences(raw);\r\n\r\n // If already valid JSON, return as-is\r\n try { JSON.parse(stripped); return stripped; } catch { /* continue */ }\r\n\r\n // Find the first '{' or '[' and extract the matching closing bracket\r\n const firstObj = stripped.indexOf('{');\r\n const firstArr = stripped.indexOf('[');\r\n const start = firstObj === -1 ? firstArr\r\n : firstArr === -1 ? firstObj\r\n : Math.min(firstObj, firstArr);\r\n\r\n if (start === -1) return stripped;\r\n\r\n const opener = stripped[start];\r\n const closer = opener === '{' ? '}' : ']';\r\n let depth = 0;\r\n let inString = false;\r\n let escape = false;\r\n\r\n for (let i = start; i < stripped.length; i++) {\r\n const ch = stripped[i];\r\n if (escape) { escape = false; continue; }\r\n if (ch === '\\\\' && inString) { escape = true; continue; }\r\n if (ch === '\"') { inString = !inString; continue; }\r\n if (inString) continue;\r\n if (ch === opener) depth++;\r\n else if (ch === closer) {\r\n depth--;\r\n if (depth === 0) return stripped.slice(start, i + 1);\r\n }\r\n }\r\n\r\n // Didn't find matching close — return from start to end (truncated, let repair handle it)\r\n return stripped.slice(start);\r\n}\r\n\r\n/**\r\n * Forbidden globals that must not appear in generated code.\r\n * Generated code runs inside `new Function('page', 'context', 'expect', code)`,\r\n * so only page, context, and expect are available anyway — but we validate\r\n * defensively to catch prompt-injection attempts.\r\n */\r\nconst FORBIDDEN_PATTERNS: ReadonlyArray<RegExp> = [\r\n /\\brequire\\s*\\(/,\r\n /\\bimport\\s*\\(/,\r\n /\\bprocess\\b/,\r\n /\\bchild_process\\b/,\r\n /\\bexec\\s*\\(/,\r\n /\\bspawn\\s*\\(/,\r\n /\\beval\\s*\\(/,\r\n /\\bFunction\\s*\\(/,\r\n /\\b__dirname\\b/,\r\n /\\b__filename\\b/,\r\n /\\bglobal\\b/,\r\n /\\bwindow\\.location\\.href\\s*=/,\r\n /\\bdocument\\.cookie\\b/,\r\n /\\blocalStorage\\b/,\r\n /\\bsessionStorage\\b/,\r\n /\\bIndexedDB\\b/,\r\n /\\bXMLHttpRequest\\b/,\r\n /\\bfetch\\s*\\(/,\r\n /\\bWebSocket\\s*\\(/,\r\n];\r\n\r\n/**\r\n * Validates that generated code is safe to execute.\r\n * Throws a ValidationError if forbidden patterns are detected.\r\n */\r\nexport function validateGeneratedCode(code: string): void {\r\n for (const pattern of FORBIDDEN_PATTERNS) {\r\n if (pattern.test(code)) {\r\n throw new ValidationError(\r\n `Generated code contains forbidden pattern: ${pattern.source}. ` +\r\n `This may be a prompt injection attempt. Please regenerate the step.`,\r\n 'generatedCode',\r\n 'UNSAFE_CODE',\r\n );\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Removes known Playwright anti-patterns from any generated code.\r\n * Exported so smart-router can apply it to template + cache hits too.\r\n *\r\n * Patterns removed:\r\n * 1. `.or(locator).first()` / `.or(locator).last()` / `.or(locator)`\r\n * AI models often chain .or() when unsure; we always prefer a single locator.\r\n * Handles one level of nested parens which covers all getBy* calls.\r\n *\r\n * 2. Standalone `.first()` / `.last()` after any locator (not method chaining)\r\n * e.g. page.getByRole('button', { name: '...' }).first().click()\r\n * → page.getByRole('button', { name: '...' }).click()\r\n */\r\n/**\r\n * Post-processes AI-generated code to fix incorrect selector choices.\r\n *\r\n * Rule: if the instruction says \"...button...\" and the code uses\r\n * page.getByText('X').click() or page.getByText(\"X\").click()\r\n * rewrite to:\r\n * page.getByRole('button', { name: 'X' }).click()\r\n *\r\n * This is a deterministic fallback for when the AI ignores prompt rules.\r\n */\r\nexport function fixButtonSelectors(instruction: string, code: string): string {\r\n const mentionsButton = /\\bbutton\\b/i.test(instruction);\r\n if (!mentionsButton) return code;\r\n\r\n // Match getByText('Name') or getByText(\"Name\") followed by .click()\r\n return code.replace(\r\n /page\\.getByText\\((['\"])(.*?)\\1\\)\\.click\\(\\)/g,\r\n (_match, _q, name: string) => `page.getByRole('button', { name: '${name}' }).click()`,\r\n );\r\n}\r\n\r\nexport function fixAntiPatterns(code: string): string {\r\n let result = code;\r\n\r\n // Remove .or(anyLocator) chains (with optional trailing .first()/.last())\r\n // Regex handles one level of nested parens: covers page.getByRole('btn', { name: 'x' })\r\n result = result.replace(\r\n /\\.or\\((?:[^()]*|\\([^()]*\\))*\\)(?:\\.(?:first|last)\\(\\))?/g,\r\n '',\r\n );\r\n\r\n // Remove leftover .first() / .last() that directly precede .click()/.fill()/etc.\r\n // i.e. locator.first().click() → locator.click()\r\n result = result.replace(/\\.(?:first|last)\\(\\)(?=\\.\\w)/g, '');\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Sanitizes AI-generated code: strips fences, removes anti-patterns,\r\n * trims whitespace, and validates against forbidden patterns.\r\n */\r\nexport function sanitizeGeneratedCode(raw: string): string {\r\n const stripped = stripCodeFences(raw);\r\n\r\n if (!stripped) {\r\n throw new ValidationError(\r\n 'AI returned an empty code response. Please try regenerating.',\r\n 'generatedCode',\r\n 'EMPTY_CODE',\r\n );\r\n }\r\n\r\n const fixed = fixAntiPatterns(stripped);\r\n validateGeneratedCode(fixed);\r\n return fixed;\r\n}\r\n\r\n/**\r\n * Sanitizes a string for safe use as a filename component.\r\n * Replaces whitespace and special chars with hyphens, lowercases.\r\n */\r\nexport function toSlug(input: string): string {\r\n return input\r\n .toLowerCase()\r\n .trim()\r\n .replace(/[^a-z0-9]+/g, '-')\r\n .replace(/^-+|-+$/g, '');\r\n}\r\n\r\n/**\r\n * Redacts secret variable values from a string so they never appear in logs.\r\n */\r\nexport function redactSecrets(\r\n text: string,\r\n secrets: ReadonlyArray<string>,\r\n): string {\r\n let result = text;\r\n for (const secret of secrets) {\r\n if (secret.length > 0) {\r\n result = result.replaceAll(secret, '[REDACTED]');\r\n }\r\n }\r\n return result;\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { StorageError } from '../utils/errors.js';\r\n\r\n/**\r\n * Atomically writes JSON to a file using a temp file + rename pattern.\r\n * This ensures the file is never left in a partially-written state.\r\n */\r\nexport async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {\r\n const dir = path.dirname(filePath);\r\n await fs.ensureDir(dir);\r\n\r\n const tmpPath = `${filePath}.${process.pid}.tmp`;\r\n try {\r\n await fs.writeJson(tmpPath, data, { spaces: 2 });\r\n await fs.rename(tmpPath, filePath);\r\n } catch (err) {\r\n // Clean up temp file on failure\r\n await fs.remove(tmpPath).catch(() => undefined);\r\n throw new StorageError(\r\n `Failed to write file \"${filePath}\": ${err instanceof Error ? err.message : String(err)}`,\r\n filePath,\r\n 'WRITE_FAILED',\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Reads and parses a JSON file, returning the parsed value.\r\n * Throws a StorageError if the file cannot be read or parsed.\r\n */\r\nexport async function readJson(filePath: string): Promise<unknown> {\r\n try {\r\n return await fs.readJson(filePath);\r\n } catch (err) {\r\n throw new StorageError(\r\n `Failed to read JSON file \"${filePath}\": ${err instanceof Error ? err.message : String(err)}`,\r\n filePath,\r\n 'READ_FAILED',\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Reads a plain text file. Throws StorageError on failure.\r\n */\r\nexport async function readText(filePath: string): Promise<string> {\r\n try {\r\n return await fs.readFile(filePath, 'utf8');\r\n } catch (err) {\r\n throw new StorageError(\r\n `Failed to read file \"${filePath}\": ${err instanceof Error ? err.message : String(err)}`,\r\n filePath,\r\n 'READ_FAILED',\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Atomically writes plain text to a file.\r\n */\r\nexport async function atomicWriteText(filePath: string, content: string): Promise<void> {\r\n const dir = path.dirname(filePath);\r\n await fs.ensureDir(dir);\r\n\r\n const tmpPath = `${filePath}.${process.pid}.tmp`;\r\n try {\r\n await fs.writeFile(tmpPath, content, 'utf8');\r\n await fs.rename(tmpPath, filePath);\r\n } catch (err) {\r\n await fs.remove(tmpPath).catch(() => undefined);\r\n throw new StorageError(\r\n `Failed to write file \"${filePath}\": ${err instanceof Error ? err.message : String(err)}`,\r\n filePath,\r\n 'WRITE_FAILED',\r\n );\r\n }\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { v4 as uuidv4 } from 'uuid';\r\nimport { TestCase, TestCaseSchema } from '../types/suite.js';\r\nimport { StorageError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { toSlug } from '../utils/sanitize.js';\r\nimport { atomicWriteJson, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('case-store');\r\n\r\nconst CASE_EXTENSION = '.test.json';\r\n\r\n/**\r\n * Reads a .test.json file from the given path.\r\n */\r\nexport async function readCase(casePath: string): Promise<TestCase> {\r\n if (!(await fs.pathExists(casePath))) {\r\n throw new StorageError(\r\n `Test case file not found at \"${casePath}\".`,\r\n casePath,\r\n 'CASE_NOT_FOUND',\r\n );\r\n }\r\n\r\n const raw = await readJson(casePath);\r\n const result = TestCaseSchema.safeParse(raw);\r\n\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Invalid test case file at \"${casePath}\":\\n${issues}`,\r\n 'case',\r\n 'INVALID_CASE',\r\n );\r\n }\r\n\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Writes a TestCase atomically to the given path.\r\n */\r\nexport async function writeCase(casePath: string, testCase: TestCase): Promise<void> {\r\n const result = TestCaseSchema.safeParse(testCase);\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid test case to \"${casePath}\":\\n${issues}`,\r\n 'case',\r\n 'INVALID_CASE',\r\n );\r\n }\r\n\r\n await atomicWriteJson(casePath, result.data);\r\n logger.debug({ caseId: testCase.id, path: casePath }, 'Test case written');\r\n}\r\n\r\n/**\r\n * Creates a new .test.json file in the given suite directory.\r\n * Returns the path to the created file.\r\n */\r\nexport async function createCase(\r\n suiteDir: string,\r\n testCase: Omit<TestCase, 'id' | 'createdAt' | 'updatedAt'>,\r\n): Promise<string> {\r\n const now = new Date().toISOString();\r\n const newCase: TestCase = {\r\n ...testCase,\r\n id: uuidv4(),\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n\r\n const casePath = path.join(suiteDir, `${toSlug(newCase.name)}${CASE_EXTENSION}`);\r\n await writeCase(casePath, newCase);\r\n logger.info({ caseId: newCase.id, path: casePath }, 'Test case created');\r\n return casePath;\r\n}\r\n\r\n/**\r\n * Updates an existing test case (merges partial fields, bumps updatedAt).\r\n */\r\nexport async function updateCase(\r\n casePath: string,\r\n updates: Partial<Omit<TestCase, 'id' | 'createdAt'>>,\r\n): Promise<TestCase> {\r\n const existing = await readCase(casePath);\r\n const updated: TestCase = {\r\n ...existing,\r\n ...updates,\r\n id: existing.id,\r\n createdAt: existing.createdAt,\r\n updatedAt: new Date().toISOString(),\r\n };\r\n await writeCase(casePath, updated);\r\n return updated;\r\n}\r\n\r\n/**\r\n * Deletes a .test.json file.\r\n */\r\nexport async function deleteCase(casePath: string): Promise<void> {\r\n if (!(await fs.pathExists(casePath))) {\r\n throw new StorageError(\r\n `Test case file not found at \"${casePath}\".`,\r\n casePath,\r\n 'CASE_NOT_FOUND',\r\n );\r\n }\r\n await fs.remove(casePath);\r\n logger.info({ path: casePath }, 'Test case deleted');\r\n}\r\n\r\n/**\r\n * Lists all .test.json file paths within a suite directory.\r\n */\r\nexport async function listCasePaths(suiteDir: string): Promise<string[]> {\r\n if (!(await fs.pathExists(suiteDir))) return [];\r\n\r\n const entries = await fs.readdir(suiteDir, { withFileTypes: true });\r\n return entries\r\n .filter((e) => e.isFile() && e.name.endsWith(CASE_EXTENSION))\r\n .map((e) => path.join(suiteDir, e.name));\r\n}\r\n\r\n/**\r\n * Reads all test cases from a suite directory.\r\n */\r\nexport async function listCases(suiteDir: string): Promise<TestCase[]> {\r\n const paths = await listCasePaths(suiteDir);\r\n const cases: TestCase[] = [];\r\n\r\n for (const casePath of paths) {\r\n try {\r\n cases.push(await readCase(casePath));\r\n } catch (err) {\r\n logger.warn({ path: casePath, err }, 'Failed to read test case — skipping');\r\n }\r\n }\r\n\r\n return cases.sort((a, b) => a.name.localeCompare(b.name));\r\n}\r\n\r\n/**\r\n * Returns the .test.json path for a case given its suite directory and case name.\r\n */\r\nexport function getCasePath(suiteDir: string, caseName: string): string {\r\n return path.join(suiteDir, `${toSlug(caseName)}${CASE_EXTENSION}`);\r\n}\r\n\r\n/**\r\n * Finds the .test.json file path for a case by its UUID.\r\n * Scans all case files in suiteDir and returns the path whose id matches.\r\n * Returns null if not found.\r\n */\r\nexport async function findCasePathById(suiteDir: string, id: string): Promise<string | null> {\r\n const paths = await listCasePaths(suiteDir);\r\n for (const casePath of paths) {\r\n try {\r\n const tc = await readCase(casePath);\r\n if (tc.id === id) return casePath;\r\n } catch {\r\n // skip malformed files\r\n }\r\n }\r\n return null;\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { VariableStore, VariableStoreSchema, ResolvedVariables, resolveVariableValue } from '../types/variable.js';\r\nimport { StorageError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { atomicWriteJson, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('variable-store');\r\n\r\nconst VARIABLES_DIR = 'variables';\r\nconst GLOBAL_FILE = 'global.json';\r\n\r\nfunction envFileName(env: string): string {\r\n return `${env}.env.json`;\r\n}\r\n\r\n/**\r\n * Reads a variables JSON file from disk and validates its shape.\r\n */\r\nexport async function readVariables(filePath: string): Promise<VariableStore> {\r\n if (!(await fs.pathExists(filePath))) {\r\n return {};\r\n }\r\n\r\n const raw = await readJson(filePath);\r\n const result = VariableStoreSchema.safeParse(raw);\r\n\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Invalid variables file at \"${filePath}\":\\n${issues}`,\r\n 'variables',\r\n 'INVALID_VARIABLES',\r\n );\r\n }\r\n\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Writes a variables file atomically.\r\n */\r\nexport async function writeVariables(filePath: string, store: VariableStore): Promise<void> {\r\n const result = VariableStoreSchema.safeParse(store);\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid variables to \"${filePath}\":\\n${issues}`,\r\n 'variables',\r\n 'INVALID_VARIABLES',\r\n );\r\n }\r\n await atomicWriteJson(filePath, result.data);\r\n logger.debug({ path: filePath }, 'Variables written');\r\n}\r\n\r\n/**\r\n * Reads the global variables file.\r\n */\r\nexport async function readGlobalVariables(rootDir: string): Promise<VariableStore> {\r\n const filePath = path.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);\r\n return readVariables(filePath);\r\n}\r\n\r\n/**\r\n * Reads environment-specific variables (e.g. dev, staging, prod).\r\n */\r\nexport async function readEnvVariables(rootDir: string, env: string): Promise<VariableStore> {\r\n const filePath = path.join(rootDir, VARIABLES_DIR, envFileName(env));\r\n return readVariables(filePath);\r\n}\r\n\r\n/**\r\n * Merges global + env-specific variables, resolving all values to strings.\r\n * Environment variables override global variables.\r\n * Secrets are resolved to their plain values (never sent to AI — caller must redact).\r\n */\r\nexport async function resolveVariables(rootDir: string, env?: string): Promise<ResolvedVariables> {\r\n const global = await readGlobalVariables(rootDir);\r\n const envSpecific = env ? await readEnvVariables(rootDir, env) : {};\r\n\r\n const merged: VariableStore = { ...global, ...envSpecific };\r\n const resolved: ResolvedVariables = {};\r\n\r\n for (const [key, value] of Object.entries(merged)) {\r\n resolved[key] = resolveVariableValue(value);\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\n/**\r\n * Sets a variable in the global variables file.\r\n */\r\nexport async function setGlobalVariable(\r\n rootDir: string,\r\n key: string,\r\n value: VariableStore[string],\r\n): Promise<void> {\r\n const filePath = path.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);\r\n await fs.ensureDir(path.dirname(filePath));\r\n const existing = await readVariables(filePath);\r\n existing[key] = value;\r\n await writeVariables(filePath, existing);\r\n}\r\n\r\n/**\r\n * Deletes a variable from the global variables file.\r\n */\r\nexport async function deleteGlobalVariable(rootDir: string, key: string): Promise<void> {\r\n const filePath = path.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);\r\n const existing = await readVariables(filePath);\r\n if (!(key in existing)) {\r\n throw new StorageError(\r\n `Variable \"${key}\" not found in global variables.`,\r\n filePath,\r\n 'VARIABLE_NOT_FOUND',\r\n );\r\n }\r\n delete existing[key];\r\n await writeVariables(filePath, existing);\r\n}\r\n\r\n/**\r\n * Lists all available variable files (global + env-specific).\r\n */\r\nexport async function listVariableFiles(rootDir: string): Promise<string[]> {\r\n const dir = path.join(rootDir, VARIABLES_DIR);\r\n if (!(await fs.pathExists(dir))) return [];\r\n\r\n const entries = await fs.readdir(dir, { withFileTypes: true });\r\n return entries\r\n .filter((e) => e.isFile() && e.name.endsWith('.json'))\r\n .map((e) => path.join(dir, e.name));\r\n}\r\n","import { z } from 'zod';\r\n\r\nexport const SecretVariableSchema = z.object({\r\n value: z.string(),\r\n secret: z.literal(true),\r\n});\r\n\r\nexport const VariableValueSchema = z.union([z.string(), SecretVariableSchema]);\r\n\r\nexport const VariableStoreSchema = z.record(z.string(), VariableValueSchema);\r\n\r\nexport type SecretVariable = z.infer<typeof SecretVariableSchema>;\r\nexport type VariableValue = z.infer<typeof VariableValueSchema>;\r\nexport type VariableStore = z.infer<typeof VariableStoreSchema>;\r\n\r\nexport interface ResolvedVariables {\r\n [key: string]: string;\r\n}\r\n\r\nexport function isSecretVariable(value: VariableValue): value is SecretVariable {\r\n return typeof value === 'object' && value.secret === true;\r\n}\r\n\r\nexport function resolveVariableValue(value: VariableValue): string {\r\n if (isSecretVariable(value)) {\r\n return value.value;\r\n }\r\n return value;\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { AutotestConfig, AutotestConfigSchema, DEFAULT_CONFIG } from '../types/config.js';\r\nimport { ConfigError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { atomicWriteJson, atomicWriteText, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('config-store');\r\n\r\n/** Runtime config is stored as JSON for reliable loading without TS compilation. */\r\nconst CONFIG_JSON = 'autotest.config.json';\r\n/** Human-readable TypeScript config kept in sync with the JSON. */\r\nconst CONFIG_TS = 'autotest.config.ts';\r\n\r\n/**\r\n * Reads the runtime config from autotest.config.json.\r\n * Falls back to DEFAULT_CONFIG if the file does not exist.\r\n */\r\nexport async function readConfig(rootDir: string): Promise<AutotestConfig> {\r\n const jsonPath = path.join(rootDir, CONFIG_JSON);\r\n\r\n if (!(await fs.pathExists(jsonPath))) {\r\n logger.debug({ rootDir }, 'No autotest.config.json found — using defaults');\r\n return DEFAULT_CONFIG;\r\n }\r\n\r\n const raw = await readJson(jsonPath);\r\n const result = AutotestConfigSchema.safeParse(raw);\r\n\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Invalid autotest.config.json at \"${jsonPath}\":\\n${issues}\\n\\n` +\r\n `How to fix: Run \"npx assuremind init\" to reset to defaults, ` +\r\n `or manually correct the file using autotest.config.ts as reference.`,\r\n 'config',\r\n 'INVALID_CONFIG',\r\n );\r\n }\r\n\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Writes the config atomically to autotest.config.json\r\n * and regenerates the human-readable autotest.config.ts.\r\n */\r\nexport async function writeConfig(rootDir: string, config: AutotestConfig): Promise<void> {\r\n const result = AutotestConfigSchema.safeParse(config);\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid config:\\n${issues}`,\r\n 'config',\r\n 'INVALID_CONFIG',\r\n );\r\n }\r\n\r\n const jsonPath = path.join(rootDir, CONFIG_JSON);\r\n await atomicWriteJson(jsonPath, result.data);\r\n\r\n const tsPath = path.join(rootDir, CONFIG_TS);\r\n await atomicWriteText(tsPath, generateConfigTs(result.data));\r\n\r\n logger.info({ rootDir }, 'Config saved');\r\n}\r\n\r\n/**\r\n * Updates specific config fields (merges, validates, and writes).\r\n */\r\nexport async function updateConfig(\r\n rootDir: string,\r\n updates: Partial<AutotestConfig>,\r\n): Promise<AutotestConfig> {\r\n const current = await readConfig(rootDir);\r\n const merged: AutotestConfig = {\r\n ...current,\r\n ...updates,\r\n healing: { ...current.healing, ...(updates.healing ?? {}) },\r\n reporting: { ...current.reporting, ...(updates.reporting ?? {}) },\r\n environmentUrls: { ...current.environmentUrls, ...(updates.environmentUrls ?? {}) },\r\n };\r\n await writeConfig(rootDir, merged);\r\n return merged;\r\n}\r\n\r\n/**\r\n * Checks whether a config exists in the given directory.\r\n */\r\nexport async function configExists(rootDir: string): Promise<boolean> {\r\n const jsonPath = path.join(rootDir, CONFIG_JSON);\r\n const tsPath = path.join(rootDir, CONFIG_TS);\r\n return (await fs.pathExists(jsonPath)) || (await fs.pathExists(tsPath));\r\n}\r\n\r\n/**\r\n * Validates the config and throws a ConfigError with actionable message if invalid.\r\n */\r\nexport async function validateConfig(rootDir: string): Promise<void> {\r\n const config = await readConfig(rootDir);\r\n\r\n if (!config.baseUrl) {\r\n throw new ConfigError(\r\n 'baseUrl is required in autotest.config.ts. ' +\r\n 'Set it to your application URL, e.g. \"http://localhost:3000\".',\r\n 'CONFIG_BASE_URL_MISSING',\r\n );\r\n }\r\n\r\n logger.debug({ config }, 'Config validated successfully');\r\n}\r\n\r\n/**\r\n * Generates the human-readable autotest.config.ts content from a config object.\r\n */\r\nfunction generateConfigTs(config: AutotestConfig): string {\r\n return `import { defineConfig } from 'assuremind';\r\n\r\nexport default defineConfig({\r\n baseUrl: '${config.baseUrl}',\r\n browsers: ${JSON.stringify(config.browsers)},\r\n headless: ${config.headless},\r\n timeout: ${config.timeout},\r\n retries: ${config.retries},\r\n parallel: ${config.parallel},\r\n screenshot: '${config.screenshot}',\r\n video: '${config.video}',\r\n trace: '${config.trace}',\r\n healing: {\r\n enabled: ${config.healing.enabled},\r\n maxLevel: ${config.healing.maxLevel},\r\n dailyBudget: ${config.healing.dailyBudget},\r\n autoPR: ${config.healing.autoPR},\r\n },\r\n reporting: {\r\n allure: ${config.reporting.allure},\r\n html: ${config.reporting.html},\r\n json: ${config.reporting.json},\r\n },\r\n studioPort: ${config.studioPort},\r\n});\r\n`;\r\n}\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { RunResult, RunResultSchema } from '../types/run.js';\r\nimport { StorageError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { atomicWriteJson, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('result-store');\r\n\r\nconst RESULTS_DIR = 'results';\r\nconst RUNS_DIR = 'runs';\r\n\r\nfunction runFilePath(resultsDir: string, runId: string): string {\r\n return path.join(resultsDir, RUNS_DIR, `${runId}.json`);\r\n}\r\n\r\n/**\r\n * Saves a run result to results/runs/{runId}.json.\r\n */\r\nexport async function writeResult(rootDir: string, result: RunResult): Promise<void> {\r\n const schema = RunResultSchema.safeParse(result);\r\n if (!schema.success) {\r\n const issues = schema.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid run result:\\n${issues}`,\r\n 'result',\r\n 'INVALID_RESULT',\r\n );\r\n }\r\n\r\n const filePath = runFilePath(path.join(rootDir, RESULTS_DIR), result.runId);\r\n await atomicWriteJson(filePath, schema.data);\r\n logger.info({ runId: result.runId, status: result.status }, 'Run result saved');\r\n}\r\n\r\n/**\r\n * Reads a run result by runId.\r\n */\r\nexport async function readResult(rootDir: string, runId: string): Promise<RunResult> {\r\n const filePath = runFilePath(path.join(rootDir, RESULTS_DIR), runId);\r\n\r\n if (!(await fs.pathExists(filePath))) {\r\n throw new StorageError(\r\n `Run result not found for runId \"${runId}\".`,\r\n filePath,\r\n 'RESULT_NOT_FOUND',\r\n );\r\n }\r\n\r\n const raw = await readJson(filePath);\r\n const result = RunResultSchema.safeParse(raw);\r\n\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Invalid run result at \"${filePath}\":\\n${issues}`,\r\n 'result',\r\n 'INVALID_RESULT',\r\n );\r\n }\r\n\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Lists all run result IDs, sorted newest-first.\r\n */\r\nexport async function listResultIds(rootDir: string): Promise<string[]> {\r\n const runsDir = path.join(rootDir, RESULTS_DIR, RUNS_DIR);\r\n if (!(await fs.pathExists(runsDir))) return [];\r\n\r\n const entries = await fs.readdir(runsDir, { withFileTypes: true });\r\n const ids = entries\r\n .filter((e) => e.isFile() && e.name.endsWith('.json'))\r\n .map((e) => e.name.replace('.json', ''));\r\n\r\n // Sort newest-first by reading mtime\r\n const withStats = await Promise.all(\r\n ids.map(async (id) => {\r\n const stat = await fs.stat(path.join(runsDir, `${id}.json`));\r\n return { id, mtime: stat.mtimeMs };\r\n }),\r\n );\r\n\r\n return withStats.sort((a, b) => b.mtime - a.mtime).map((x) => x.id);\r\n}\r\n\r\n/**\r\n * Lists run results (metadata only — no step details) for dashboard display.\r\n */\r\nexport async function listResults(\r\n rootDir: string,\r\n limit = 20,\r\n): Promise<(Pick<RunResult, 'runId' | 'status' | 'environment' | 'startedAt' | 'finishedAt' | 'totalTests' | 'passed' | 'failed' | 'skipped' | 'duration'> & { suiteIds: string[] })[]> {\r\n const ids = await listResultIds(rootDir);\r\n const results = [];\r\n\r\n for (const id of ids.slice(0, limit)) {\r\n try {\r\n const result = await readResult(rootDir, id);\r\n results.push({\r\n runId: result.runId,\r\n status: result.status,\r\n environment: result.environment,\r\n startedAt: result.startedAt,\r\n finishedAt: result.finishedAt,\r\n totalTests: result.totalTests,\r\n passed: result.passed,\r\n failed: result.failed,\r\n skipped: result.skipped,\r\n duration: result.duration,\r\n suiteIds: result.suites.map((s) => s.suiteId),\r\n });\r\n } catch (err) {\r\n logger.warn({ runId: id, err }, 'Failed to read run result — skipping');\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Deletes a run result file.\r\n */\r\nexport async function deleteResult(rootDir: string, runId: string): Promise<void> {\r\n const filePath = runFilePath(path.join(rootDir, RESULTS_DIR), runId);\r\n if (!(await fs.pathExists(filePath))) {\r\n throw new StorageError(\r\n `Run result not found for runId \"${runId}\".`,\r\n filePath,\r\n 'RESULT_NOT_FOUND',\r\n );\r\n }\r\n await fs.remove(filePath);\r\n logger.info({ runId }, 'Run result deleted');\r\n}\r\n\r\n/**\r\n * Returns the screenshots directory path for a run.\r\n */\r\nexport function screenshotsDir(rootDir: string): string {\r\n return path.join(rootDir, RESULTS_DIR, 'screenshots');\r\n}\r\n\r\n/**\r\n * Returns the videos directory path.\r\n */\r\nexport function videosDir(rootDir: string): string {\r\n return path.join(rootDir, RESULTS_DIR, 'videos');\r\n}\r\n\r\n/**\r\n * Returns the traces directory path.\r\n */\r\nexport function tracesDir(rootDir: string): string {\r\n return path.join(rootDir, RESULTS_DIR, 'traces');\r\n}\r\n","import { z } from 'zod';\r\n\r\nexport const RunStatusSchema = z.enum(['pending', 'running', 'passed', 'failed', 'skipped']);\r\nexport type RunStatus = z.infer<typeof RunStatusSchema>;\r\n\r\nexport interface RunConfig {\r\n all?: boolean;\r\n type?: string; // Filter suites by suite type (ui, api, audit, performance)\r\n suite?: string;\r\n tag?: string;\r\n test?: string;\r\n browsers?: string[];\r\n env?: string;\r\n parallel?: number;\r\n headed?: boolean;\r\n ci?: boolean;\r\n reporter?: string[];\r\n noHealing?: boolean;\r\n screenshot?: 'off' | 'on' | 'only-on-failure';\r\n video?: 'off' | 'on' | 'on-first-retry' | 'retain-on-failure';\r\n trace?: 'off' | 'on' | 'on-first-retry' | 'retain-on-failure';\r\n /** Playwright device descriptor name (e.g. 'iPhone 15 Pro') — undefined = no emulation. */\r\n device?: string;\r\n}\r\n\r\nexport const ApiRequestSchema = z.object({\r\n method: z.string(),\r\n url: z.string(),\r\n headers: z.record(z.string()).optional(),\r\n body: z.string().optional(),\r\n}).optional();\r\n\r\nexport const ApiResponseSchema = z.object({\r\n status: z.number(),\r\n statusText: z.string(),\r\n headers: z.record(z.string()).optional(),\r\n body: z.string().optional(),\r\n duration: z.number(),\r\n}).optional();\r\n\r\n// Helper: accept number | null | undefined, always store as number | undefined\r\nconst optionalNum = z.number().nullish().transform((v) => v ?? undefined);\r\n\r\n// Individual Lighthouse audit result (pass / partial / fail / not-applicable)\r\nexport const AuditItemSchema = z.object({\r\n id: z.string(),\r\n title: z.string(),\r\n passed: z.boolean(), // true = score === 1\r\n partial: z.boolean().optional(), // true = 0 < score < 1 (needs work)\r\n na: z.boolean().optional(), // true = score === null (not applicable)\r\n});\r\nexport type AuditItem = z.infer<typeof AuditItemSchema>;\r\n\r\nexport const PageLoadMetricSchema = z.object({\r\n url: z.string(),\r\n stepIndex: z.number().int(),\r\n stepInstruction: z.string(),\r\n score: optionalNum, // Performance score 0-100 (pre-multiplied in runner)\r\n a11yScore: optionalNum, // Accessibility score 0-100 (pre-multiplied in runner)\r\n seoScore: optionalNum, // SEO score 0-100 (pre-multiplied in runner)\r\n fcp: optionalNum, // First Contentful Paint (ms)\r\n lcp: optionalNum, // Largest Contentful Paint (ms)\r\n cls: optionalNum, // Cumulative Layout Shift (score)\r\n ttfb: optionalNum, // Time to First Byte (ms)\r\n tbt: optionalNum, // Total Blocking Time (ms)\r\n si: optionalNum, // Speed Index (ms)\r\n tti: optionalNum, // Time to Interactive (ms)\r\n inp: optionalNum, // Interaction to Next Paint (ms)\r\n // Individual audit items for Accessibility and SEO categories\r\n a11yAudits: z.array(AuditItemSchema).optional(),\r\n seoAudits: z.array(AuditItemSchema).optional(),\r\n lighthouseError: z.string().optional(),\r\n});\r\nexport type PageLoadMetric = z.infer<typeof PageLoadMetricSchema>;\r\n\r\nexport const StepResultSchema = z.object({\r\n stepId: z.string(),\r\n instruction: z.string(),\r\n status: RunStatusSchema,\r\n code: z.string(),\r\n error: z.string().optional(),\r\n duration: z.number(),\r\n screenshotPath: z.string().optional(),\r\n healed: z.boolean().optional(),\r\n healedCode: z.string().optional(),\r\n stepType: z.enum(['ui', 'api', 'mock']).optional(),\r\n apiRequest: ApiRequestSchema,\r\n apiResponse: ApiResponseSchema,\r\n navigatedToUrl: z.string().optional(),\r\n auditUrl: z.string().optional(), // URL captured when step has runAudit=true (may not have navigated)\r\n});\r\n\r\nexport const TestCaseResultSchema = z.object({\r\n caseId: z.string(),\r\n caseName: z.string(),\r\n status: RunStatusSchema,\r\n steps: z.array(StepResultSchema),\r\n duration: z.number(),\r\n browser: z.string(),\r\n /** Playwright device descriptor used for this case (undefined = no emulation). */\r\n device: z.string().optional(),\r\n startedAt: z.string().datetime(),\r\n finishedAt: z.string().datetime(),\r\n videoPath: z.string().optional(),\r\n tracePath: z.string().optional(),\r\n dataRowIndex: z.number().int().optional(),\r\n dataRow: z.record(z.string()).optional(),\r\n pageLoads: z.array(PageLoadMetricSchema).default([]),\r\n});\r\n\r\nexport const SuiteResultSchema = z.object({\r\n suiteId: z.string(),\r\n suiteName: z.string(),\r\n suiteType: z.enum(['ui', 'api', 'audit', 'performance']).optional(),\r\n status: RunStatusSchema,\r\n cases: z.array(TestCaseResultSchema),\r\n duration: z.number(),\r\n browser: z.string(),\r\n startedAt: z.string().datetime(),\r\n finishedAt: z.string().datetime(),\r\n});\r\n\r\nexport const RunResultSchema = z.object({\r\n runId: z.string(),\r\n status: RunStatusSchema,\r\n environment: z.string().optional(),\r\n suites: z.array(SuiteResultSchema),\r\n duration: z.number(),\r\n startedAt: z.string().datetime(),\r\n finishedAt: z.string().datetime(),\r\n totalTests: z.number().int(),\r\n passed: z.number().int(),\r\n failed: z.number().int(),\r\n skipped: z.number().int(),\r\n logFilePath: z.string().optional(),\r\n});\r\n\r\nexport type StepResult = z.infer<typeof StepResultSchema>;\r\nexport type TestCaseResult = z.infer<typeof TestCaseResultSchema>;\r\nexport type SuiteResult = z.infer<typeof SuiteResultSchema>;\r\nexport type RunResult = z.infer<typeof RunResultSchema>;\r\n","import path from 'path';\r\nimport fs from 'fs-extra';\r\nimport { HealingEvent, HealingEventSchema, HealingReport, HealingReportSchema } from '../types/healing.js';\r\nimport { StorageError, ValidationError } from '../utils/errors.js';\r\nimport { createChildLogger } from '../utils/logger.js';\r\nimport { atomicWriteJson, readJson } from './utils.js';\r\n\r\nconst logger = createChildLogger('healing-store');\r\n\r\nconst HEALING_DIR = path.join('results', 'healing');\r\nconst PENDING_FILE = 'pending.json';\r\nconst REPORT_PREFIX = 'healing-report-';\r\n\r\nfunction reportFilePath(healingDir: string, runId: string): string {\r\n return path.join(healingDir, `${REPORT_PREFIX}${runId}.json`);\r\n}\r\n\r\nfunction pendingFilePath(healingDir: string): string {\r\n return path.join(healingDir, PENDING_FILE);\r\n}\r\n\r\n/**\r\n * Saves a complete healing report for a run.\r\n */\r\nexport async function writeHealingReport(\r\n rootDir: string,\r\n report: HealingReport,\r\n): Promise<void> {\r\n const schema = HealingReportSchema.safeParse(report);\r\n if (!schema.success) {\r\n const issues = schema.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Cannot write invalid healing report:\\n${issues}`,\r\n 'healingReport',\r\n 'INVALID_HEALING_REPORT',\r\n );\r\n }\r\n\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n const filePath = reportFilePath(healingDir, report.runId);\r\n await atomicWriteJson(filePath, schema.data);\r\n logger.info({ runId: report.runId, totalHeals: report.totalHeals }, 'Healing report saved');\r\n}\r\n\r\n/**\r\n * Reads a healing report for a specific run.\r\n */\r\nexport async function readHealingReport(rootDir: string, runId: string): Promise<HealingReport> {\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n const filePath = reportFilePath(healingDir, runId);\r\n\r\n if (!(await fs.pathExists(filePath))) {\r\n throw new StorageError(\r\n `Healing report not found for runId \"${runId}\".`,\r\n filePath,\r\n 'HEALING_REPORT_NOT_FOUND',\r\n );\r\n }\r\n\r\n const raw = await readJson(filePath);\r\n const result = HealingReportSchema.safeParse(raw);\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ValidationError(\r\n `Invalid healing report at \"${filePath}\":\\n${issues}`,\r\n 'healingReport',\r\n 'INVALID_HEALING_REPORT',\r\n );\r\n }\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Reads all pending healing events across all runs.\r\n */\r\nexport async function readPendingEvents(rootDir: string): Promise<HealingEvent[]> {\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n const filePath = pendingFilePath(healingDir);\r\n if (!(await fs.pathExists(filePath))) return [];\r\n\r\n const raw = await readJson(filePath);\r\n const result = HealingEventSchema.array().safeParse(raw);\r\n if (!result.success) {\r\n logger.warn({ path: filePath }, 'Pending healing file is malformed — resetting');\r\n return [];\r\n }\r\n return result.data;\r\n}\r\n\r\nconst MAX_HEALING_EVENTS = 50;\r\n\r\n/**\r\n * Priority order for auto-pruning when the cap is exceeded.\r\n * Lower index = deleted first.\r\n */\r\nconst PRUNE_PRIORITY: HealingEvent['status'][] = ['pending', 'rejected', 'accepted'];\r\n\r\n/**\r\n * Trims events to MAX_HEALING_EVENTS, removing by PRUNE_PRIORITY (oldest first\r\n * within the same status group) until the list fits.\r\n */\r\nfunction pruneToLimit(events: HealingEvent[]): HealingEvent[] {\r\n if (events.length <= MAX_HEALING_EVENTS) return events;\r\n\r\n // Build a mutable copy sorted so oldest entries of lower-priority statuses come first\r\n const byPriority = [...events].sort((a, b) => {\r\n const pa = PRUNE_PRIORITY.indexOf(a.status);\r\n const pb = PRUNE_PRIORITY.indexOf(b.status);\r\n if (pa !== pb) return pa - pb; // lower priority (pending) first\r\n return a.timestamp < b.timestamp ? -1 : 1; // oldest first within same status\r\n });\r\n\r\n const excess = events.length - MAX_HEALING_EVENTS;\r\n const toDelete = new Set(byPriority.slice(0, excess).map((e) => e.id));\r\n\r\n logger.info(\r\n { excess, deleted: toDelete.size },\r\n 'Auto-pruning healing events to enforce cap',\r\n );\r\n\r\n return events.filter((e) => !toDelete.has(e.id));\r\n}\r\n\r\n/**\r\n * Appends a new healing event to the pending list, then enforces the 50-event cap.\r\n */\r\nexport async function appendPendingEvent(rootDir: string, event: HealingEvent): Promise<void> {\r\n const schema = HealingEventSchema.safeParse(event);\r\n if (!schema.success) {\r\n throw new ValidationError(\r\n `Invalid healing event: ${schema.error.message}`,\r\n 'healingEvent',\r\n 'INVALID_HEALING_EVENT',\r\n );\r\n }\r\n\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await fs.ensureDir(healingDir);\r\n\r\n const existing = await readPendingEvents(rootDir);\r\n existing.push(schema.data);\r\n await atomicWriteJson(pendingFilePath(healingDir), pruneToLimit(existing));\r\n}\r\n\r\n/**\r\n * Accepts a pending healing event: updates status and persists.\r\n */\r\nexport async function acceptHealingEvent(rootDir: string, eventId: string): Promise<HealingEvent> {\r\n const pending = await readPendingEvents(rootDir);\r\n const idx = pending.findIndex((e) => e.id === eventId);\r\n\r\n if (idx === -1) {\r\n throw new StorageError(\r\n `Healing event \"${eventId}\" not found in pending list.`,\r\n eventId,\r\n 'HEALING_EVENT_NOT_FOUND',\r\n );\r\n }\r\n\r\n pending[idx] = {\r\n ...pending[idx],\r\n status: 'accepted',\r\n acceptedAt: new Date().toISOString(),\r\n } as HealingEvent;\r\n\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), pending);\r\n logger.info({ eventId }, 'Healing event accepted');\r\n\r\n return pending[idx];\r\n}\r\n\r\n/**\r\n * Rejects a pending healing event.\r\n */\r\nexport async function rejectHealingEvent(rootDir: string, eventId: string): Promise<HealingEvent> {\r\n const pending = await readPendingEvents(rootDir);\r\n const idx = pending.findIndex((e) => e.id === eventId);\r\n\r\n if (idx === -1) {\r\n throw new StorageError(\r\n `Healing event \"${eventId}\" not found in pending list.`,\r\n eventId,\r\n 'HEALING_EVENT_NOT_FOUND',\r\n );\r\n }\r\n\r\n pending[idx] = {\r\n ...pending[idx],\r\n status: 'rejected',\r\n rejectedAt: new Date().toISOString(),\r\n } as HealingEvent;\r\n\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), pending);\r\n logger.info({ eventId }, 'Healing event rejected');\r\n\r\n return pending[idx];\r\n}\r\n\r\n/**\r\n * Deletes a single healing event by ID.\r\n */\r\nexport async function deleteHealingEvent(rootDir: string, eventId: string): Promise<void> {\r\n const events = await readPendingEvents(rootDir);\r\n const filtered = events.filter((e) => e.id !== eventId);\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), filtered);\r\n logger.info({ eventId }, 'Healing event deleted');\r\n}\r\n\r\n/**\r\n * Deletes multiple healing events by their IDs.\r\n * Returns the number of events actually deleted.\r\n */\r\nexport async function deleteHealingEvents(rootDir: string, eventIds: string[]): Promise<number> {\r\n const events = await readPendingEvents(rootDir);\r\n const idSet = new Set(eventIds);\r\n const filtered = events.filter((e) => !idSet.has(e.id));\r\n const deleted = events.length - filtered.length;\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), filtered);\r\n logger.info({ count: deleted }, 'Healing events bulk-deleted');\r\n return deleted;\r\n}\r\n\r\n/**\r\n * Deletes ALL healing events.\r\n * Returns the number of events deleted.\r\n */\r\nexport async function clearAllHealingEvents(rootDir: string): Promise<number> {\r\n const events = await readPendingEvents(rootDir);\r\n const count = events.length;\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), []);\r\n logger.info({ count }, 'All healing events cleared');\r\n return count;\r\n}\r\n\r\n/**\r\n * Removes accepted and rejected events older than retentionDays from the pending file.\r\n */\r\nexport async function pruneResolvedEvents(\r\n rootDir: string,\r\n retentionDays = 30,\r\n): Promise<number> {\r\n const pending = await readPendingEvents(rootDir);\r\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;\r\n const before = pending.length;\r\n\r\n const kept = pending.filter((e) => {\r\n if (e.status === 'pending') return true;\r\n const resolvedAt = e.acceptedAt ?? e.rejectedAt;\r\n if (!resolvedAt) return true;\r\n return new Date(resolvedAt).getTime() > cutoff;\r\n });\r\n\r\n if (kept.length < before) {\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n await atomicWriteJson(pendingFilePath(healingDir), kept);\r\n }\r\n\r\n return before - kept.length;\r\n}\r\n\r\n/**\r\n * Returns summary statistics for the healing store.\r\n */\r\nexport async function getHealingStats(rootDir: string): Promise<{\r\n pending: number;\r\n accepted: number;\r\n rejected: number;\r\n total: number;\r\n}> {\r\n const events = await readPendingEvents(rootDir);\r\n const pending = events.filter((e) => e.status === 'pending').length;\r\n const accepted = events.filter((e) => e.status === 'accepted').length;\r\n const rejected = events.filter((e) => e.status === 'rejected').length;\r\n return { pending, accepted, rejected, total: events.length };\r\n}\r\n\r\n/**\r\n * Lists all healing report runIds (newest-first by mtime).\r\n */\r\nexport async function listHealingReportIds(rootDir: string): Promise<string[]> {\r\n const healingDir = path.join(rootDir, HEALING_DIR);\r\n if (!(await fs.pathExists(healingDir))) return [];\r\n\r\n const entries = await fs.readdir(healingDir, { withFileTypes: true });\r\n const ids = entries\r\n .filter((e) => e.isFile() && e.name.startsWith(REPORT_PREFIX) && e.name.endsWith('.json'))\r\n .map((e) => e.name.slice(REPORT_PREFIX.length, -'.json'.length));\r\n\r\n const withStats = await Promise.all(\r\n ids.map(async (id) => {\r\n const stat = await fs.stat(path.join(healingDir, `${REPORT_PREFIX}${id}.json`));\r\n return { id, mtime: stat.mtimeMs };\r\n }),\r\n );\r\n\r\n return withStats.sort((a, b) => b.mtime - a.mtime).map((x) => x.id);\r\n}\r\n","import { z } from 'zod';\r\n\r\nexport const HealingStatusSchema = z.enum(['pending', 'accepted', 'rejected']);\r\nexport const HealingStrategySchema = z.enum([\r\n 'retry',\r\n 'regenerate',\r\n 'multi-selector',\r\n 'visual',\r\n 'decompose',\r\n 'manual',\r\n]);\r\n\r\nexport type HealingStatus = z.infer<typeof HealingStatusSchema>;\r\nexport type HealingStrategy = z.infer<typeof HealingStrategySchema>;\r\n\r\nexport const HealingEventSchema = z.object({\r\n id: z.string(),\r\n runId: z.string(),\r\n suiteId: z.string(),\r\n caseId: z.string(),\r\n stepId: z.string(),\r\n stepInstruction: z.string(),\r\n failedCode: z.string(),\r\n healedCode: z.string(),\r\n error: z.string(),\r\n strategy: HealingStrategySchema,\r\n level: z.number().int().min(1).max(6),\r\n status: HealingStatusSchema,\r\n pageUrl: z.string(),\r\n timestamp: z.string().datetime(),\r\n acceptedAt: z.string().datetime().optional(),\r\n rejectedAt: z.string().datetime().optional(),\r\n});\r\n\r\nexport const HealingReportSchema = z.object({\r\n runId: z.string(),\r\n generatedAt: z.string().datetime(),\r\n totalHeals: z.number().int(),\r\n accepted: z.number().int(),\r\n rejected: z.number().int(),\r\n pending: z.number().int(),\r\n events: z.array(HealingEventSchema),\r\n});\r\n\r\nexport type HealingEvent = z.infer<typeof HealingEventSchema>;\r\nexport type HealingReport = z.infer<typeof HealingReportSchema>;\r\n\r\nexport interface HealingDecision {\r\n heal: boolean;\r\n strategy?: 'individual' | 'batch';\r\n reason: string;\r\n}\r\n\r\nexport type FailureKind = 'assertion' | 'infra';\r\n\r\nexport interface StepFailure {\r\n stepId: string;\r\n instruction: string;\r\n error: string;\r\n pageUrl: string;\r\n failedCode: string;\r\n failureKind: FailureKind;\r\n}\r\n","import { createHash } from 'crypto';\r\n\r\nexport function sha256(input: string): string {\r\n return createHash('sha256').update(input, 'utf8').digest('hex');\r\n}\r\n\r\nexport function md5(input: string): string {\r\n return createHash('md5').update(input, 'utf8').digest('hex');\r\n}\r\n\r\n/**\r\n * Normalises a URL by replacing dynamic path segments (IDs) with wildcards.\r\n * Example: /users/123/edit becomes /users/{star}/edit\r\n * Example: /posts/abc-def/comments becomes /posts/{star}/comments\r\n */\r\nexport function normalizeUrl(url: string): string {\r\n try {\r\n const parsed = new URL(url);\r\n const normalizedPath = parsed.pathname\r\n .split('/')\r\n .map((segment) => {\r\n // Numeric IDs\r\n if (/^\\d+$/.test(segment)) return '*';\r\n // UUIDs\r\n if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {\r\n return '*';\r\n }\r\n // Slugs that look like IDs (alphanumeric + dashes, 8+ chars)\r\n if (/^[a-z0-9-]{8,}$/i.test(segment) && /-/.test(segment)) return '*';\r\n return segment;\r\n })\r\n .join('/');\r\n return normalizedPath;\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\n/**\r\n * Generates a short cache key from an instruction and URL.\r\n * Key = first 16 chars of sha256(normalizedInstruction|urlPattern)\r\n */\r\nexport function generateCacheKey(instruction: string, url: string): string {\r\n const normalizedInstruction = instruction.toLowerCase().replace(/\\s+/g, ' ').trim();\r\n const urlPattern = normalizeUrl(url);\r\n return sha256(`${normalizedInstruction}|${urlPattern}`).slice(0, 16);\r\n}\r\n","import { config as loadDotenv } from 'dotenv';\r\nimport { z } from 'zod';\r\nimport { ConfigError } from './errors.js';\r\n\r\nloadDotenv();\r\n\r\nconst AI_PROVIDERS = [\r\n 'anthropic',\r\n 'openai',\r\n 'google',\r\n 'azure-openai',\r\n 'bedrock',\r\n 'deepseek',\r\n 'groq',\r\n 'together',\r\n 'qwen',\r\n 'perplexity',\r\n 'ollama',\r\n 'custom',\r\n] as const;\r\n\r\nexport type AIProviderName = (typeof AI_PROVIDERS)[number];\r\n\r\nconst BaseEnvSchema = z.object({\r\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\r\n LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),\r\n AI_PROVIDER: z.enum(AI_PROVIDERS),\r\n AI_TIERED_ENABLED: z\r\n .string()\r\n .transform((v) => v === 'true')\r\n .default('false'),\r\n AI_TIERED_FAST_PROVIDER: z.enum(AI_PROVIDERS).optional(),\r\n AI_TIERED_FAST_MODEL: z.string().optional(),\r\n AI_TIMEOUT: z.coerce.number().int().positive().default(30),\r\n AI_MAX_RETRIES: z.coerce.number().int().min(0).max(10).default(2),\r\n});\r\n\r\nconst AnthropicEnvSchema = z.object({\r\n ANTHROPIC_API_KEY: z.string().min(1),\r\n ANTHROPIC_MODEL: z.string().default('claude-sonnet-4-20250514'),\r\n});\r\n\r\nconst OpenAIEnvSchema = z.object({\r\n OPENAI_API_KEY: z.string().min(1),\r\n OPENAI_MODEL: z.string().default('gpt-4o'),\r\n});\r\n\r\nconst GoogleEnvSchema = z.object({\r\n GOOGLE_API_KEY: z.string().min(1),\r\n GOOGLE_MODEL: z.string().default('gemini-2.5-pro'),\r\n});\r\n\r\nconst AzureOpenAIEnvSchema = z.object({\r\n AZURE_OPENAI_API_KEY: z.string().min(1),\r\n AZURE_OPENAI_ENDPOINT: z.string().url(),\r\n AZURE_OPENAI_DEPLOYMENT: z.string().min(1),\r\n AZURE_OPENAI_API_VERSION: z.string().default('2024-10-21'),\r\n});\r\n\r\nconst BedrockEnvSchema = z.object({\r\n AWS_ACCESS_KEY_ID: z.string().min(1),\r\n AWS_SECRET_ACCESS_KEY: z.string().min(1),\r\n AWS_SESSION_TOKEN: z.string().optional(),\r\n AWS_REGION: z.string().default('us-east-1'),\r\n BEDROCK_MODEL: z.string().default('anthropic.claude-sonnet-4-20250514-v1:0'),\r\n});\r\n\r\nconst DeepSeekEnvSchema = z.object({\r\n DEEPSEEK_API_KEY: z.string().min(1),\r\n DEEPSEEK_MODEL: z.string().default('deepseek-chat'),\r\n});\r\n\r\nconst GroqEnvSchema = z.object({\r\n GROQ_API_KEY: z.string().min(1),\r\n GROQ_MODEL: z.string().default('llama-3.3-70b-versatile'),\r\n});\r\n\r\nconst TogetherEnvSchema = z.object({\r\n TOGETHER_API_KEY: z.string().min(1),\r\n TOGETHER_MODEL: z.string().default('meta-llama/Llama-3.3-70B-Instruct-Turbo'),\r\n});\r\n\r\nconst QwenEnvSchema = z.object({\r\n QWEN_API_KEY: z.string().min(1),\r\n QWEN_BASE_URL: z.string().url().default('https://dashscope-intl.aliyuncs.com/compatible-mode/v1'),\r\n QWEN_MODEL: z.string().default('qwen-max'),\r\n});\r\n\r\nconst PerplexityEnvSchema = z.object({\r\n PERPLEXITY_API_KEY: z.string().min(1),\r\n PERPLEXITY_MODEL: z.string().default('sonar-pro'),\r\n});\r\n\r\nconst OllamaEnvSchema = z.object({\r\n OLLAMA_BASE_URL: z.string().url().default('http://localhost:11434'),\r\n OLLAMA_MODEL: z.string().default('llama3.3'),\r\n});\r\n\r\nconst CustomEnvSchema = z.object({\r\n CUSTOM_API_KEY: z.string().min(1),\r\n CUSTOM_BASE_URL: z.string().url(),\r\n CUSTOM_MODEL: z.string().min(1),\r\n});\r\n\r\nconst PROVIDER_SCHEMAS: Record<AIProviderName, z.ZodObject<z.ZodRawShape>> = {\r\n anthropic: AnthropicEnvSchema,\r\n openai: OpenAIEnvSchema,\r\n google: GoogleEnvSchema,\r\n 'azure-openai': AzureOpenAIEnvSchema,\r\n bedrock: BedrockEnvSchema,\r\n deepseek: DeepSeekEnvSchema,\r\n groq: GroqEnvSchema,\r\n together: TogetherEnvSchema,\r\n qwen: QwenEnvSchema,\r\n perplexity: PerplexityEnvSchema,\r\n ollama: OllamaEnvSchema,\r\n custom: CustomEnvSchema,\r\n};\r\n\r\nexport type BaseEnv = z.infer<typeof BaseEnvSchema>;\r\nexport type ValidatedEnv = BaseEnv & Record<string, unknown>;\r\n\r\nlet _cachedEnv: ValidatedEnv | null = null;\r\n\r\n/**\r\n * Loads and validates the environment for the configured AI provider.\r\n * Throws a ConfigError with actionable message if validation fails.\r\n * Result is cached after first call.\r\n */\r\nexport function validateEnv(): ValidatedEnv {\r\n if (_cachedEnv !== null) return _cachedEnv;\r\n\r\n const baseResult = BaseEnvSchema.safeParse(process.env);\r\n if (!baseResult.success) {\r\n const issues = baseResult.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ConfigError(\r\n `Missing or invalid environment variables:\\n${issues}\\n\\n` +\r\n `How to fix: Copy .env.example to .env and fill in your AI provider credentials.`,\r\n 'ENV_VALIDATION_FAILED',\r\n );\r\n }\r\n\r\n const providerName = baseResult.data.AI_PROVIDER;\r\n const providerSchema = PROVIDER_SCHEMAS[providerName];\r\n const providerResult = providerSchema.safeParse(process.env);\r\n\r\n if (!providerResult.success) {\r\n const issues = providerResult.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new ConfigError(\r\n `Provider \"${providerName}\" is missing required environment variables:\\n${issues}\\n\\n` +\r\n `How to fix: Check .env.example for the ${providerName} section and add the required keys to .env.`,\r\n 'PROVIDER_ENV_MISSING',\r\n );\r\n }\r\n\r\n const merged: ValidatedEnv = {\r\n ...baseResult.data,\r\n ...(providerResult.data as Record<string, unknown>),\r\n };\r\n _cachedEnv = merged;\r\n\r\n return _cachedEnv;\r\n}\r\n\r\n/** Clears the cached env — used in tests to reset state between test cases. */\r\nexport function clearEnvCache(): void {\r\n _cachedEnv = null;\r\n}\r\n\r\n/** Returns the validated env without throwing — for non-critical reads. */\r\nexport function tryGetEnv(): ValidatedEnv | null {\r\n try {\r\n return validateEnv();\r\n } catch {\r\n return null;\r\n }\r\n}\r\n","import type { AutotestConfig } from './types/config.js';\r\nimport { AutotestConfigSchema, DEFAULT_CONFIG } from './types/config.js';\r\n\r\n/**\r\n * defineConfig — type-safe helper for autotest.config.ts.\r\n * Validates the config at definition time and returns it unchanged.\r\n * Usage: export default defineConfig({ baseUrl: '...', ... })\r\n */\r\nexport function defineConfig(config: Partial<AutotestConfig>): AutotestConfig {\r\n const merged = { ...DEFAULT_CONFIG, ...config };\r\n const result = AutotestConfigSchema.safeParse(merged);\r\n if (!result.success) {\r\n const issues = result.error.issues\r\n .map((i) => ` • ${i.path.join('.')}: ${i.message}`)\r\n .join('\\n');\r\n throw new Error(`Invalid assuremind config:\\n${issues}`);\r\n }\r\n return result.data;\r\n}\r\n\r\n// Public types\r\nexport type {\r\n AutotestConfig,\r\n HealingConfig,\r\n ReportingConfig,\r\n BrowserName,\r\n ScreenshotMode,\r\n VideoMode,\r\n TraceMode,\r\n} from './types/config.js';\r\n\r\nexport type {\r\n TestSuite,\r\n TestCase,\r\n TestStep,\r\n Priority,\r\n GenerationStrategy,\r\n} from './types/suite.js';\r\n\r\nexport type {\r\n RunResult,\r\n SuiteResult,\r\n TestCaseResult,\r\n StepResult,\r\n RunStatus,\r\n RunConfig,\r\n} from './types/run.js';\r\n\r\nexport type {\r\n HealingEvent,\r\n HealingReport,\r\n HealingDecision,\r\n HealingStatus,\r\n HealingStrategy,\r\n StepFailure,\r\n} from './types/healing.js';\r\n\r\nexport type {\r\n VariableStore,\r\n VariableValue,\r\n SecretVariable,\r\n ResolvedVariables,\r\n} from './types/variable.js';\r\n\r\nexport type {\r\n AIProvider,\r\n PageContext,\r\n GenerationResult,\r\n GeneratedSuite,\r\n GeneratedCase,\r\n GeneratedStep,\r\n RouterStats,\r\n} from './types/ai.js';\r\n\r\n// Storage API (for programmatic use)\r\nexport * from './storage/index.js';\r\n\r\n// Utilities\r\nexport { createChildLogger, logger } from './utils/logger.js';\r\nexport {\r\n AssuremindError,\r\n ProviderError,\r\n ExecutionError,\r\n ConfigError,\r\n ValidationError,\r\n HealingError,\r\n StorageError,\r\n isAssuremindError,\r\n formatError,\r\n} from './utils/errors.js';\r\nexport { sanitizeGeneratedCode, toSlug, redactSecrets } from './utils/sanitize.js';\r\nexport { generateCacheKey, normalizeUrl, sha256 } from './utils/hash.js';\r\nexport { validateEnv, clearEnvCache, tryGetEnv } from './utils/env.js';\r\n"],"mappings":"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","names":["path","fs","z","path","fs","fs","fs","logger","path","fs","logger","fs","path","fs","uuidv4","logger","fs","uuidv4","path","path","fs","z","logger","fs","path","path","fs","logger","path","fs","path","fs","z","logger","path","fs","path","fs","z","logger","path","fs","path","fs","z","z"]}
@@ -0,0 +1,312 @@
1
+ # CLI Reference
2
+
3
+ All commands are invoked via `npx assuremind <command>` or, after a global install, `assuremind <command>`.
4
+
5
+ ---
6
+
7
+ ## Global flags
8
+
9
+ | Flag | Description |
10
+ |------|-------------|
11
+ | `-v, --version` | Print version number |
12
+ | `-h, --help` | Show help for any command |
13
+
14
+ ---
15
+
16
+ ## `init`
17
+
18
+ Initialise Assuremind in the current directory.
19
+
20
+ ```bash
21
+ npx assuremind init [options]
22
+ ```
23
+
24
+ | Option | Description |
25
+ |--------|-------------|
26
+ | `--skip-playwright` | Skip Playwright browser installation (useful in CI) |
27
+
28
+ **What it does:**
29
+
30
+ - Validates that a `package.json` exists
31
+ - Creates directories: `tests/`, `variables/`, `results/`, `fixtures/`
32
+ - Copies template files (skips any that already exist):
33
+ - `.env.example` — full AI provider reference
34
+ - `.env` — minimal config to fill in
35
+ - `.gitignore` — ignores `.env`, `node_modules`, `results/`
36
+ - `autotest.config.ts` — framework configuration
37
+ - `variables/global.json` — empty variable store
38
+ - `TESTAUTOMIND.md` — quick-reference card
39
+ - Installs Playwright browsers unless `--skip-playwright` is given
40
+ - Runs `npx playwright install` for chromium, firefox, webkit
41
+
42
+ **Example:**
43
+
44
+ ```bash
45
+ cd my-project
46
+ npm install assuremind
47
+ npx assuremind init
48
+ ```
49
+
50
+ ---
51
+
52
+ ## `studio`
53
+
54
+ Open the Assuremind web UI.
55
+
56
+ ```bash
57
+ npx assuremind studio [options]
58
+ ```
59
+
60
+ | Option | Description |
61
+ |--------|-------------|
62
+ | `-p, --port <number>` | Override the port (default: value from `autotest.config.ts` → `studioPort`, fallback 4400) |
63
+ | `--no-open` | Start the server but do not open the browser automatically |
64
+
65
+ **Example:**
66
+
67
+ ```bash
68
+ npx assuremind studio
69
+ npx assuremind studio --port 5000
70
+ npx assuremind studio --no-open # headless server for remote access
71
+ ```
72
+
73
+ ---
74
+
75
+ ## `run`
76
+
77
+ Run tests from the command line.
78
+
79
+ ```bash
80
+ npx assuremind run [filters] [options]
81
+ ```
82
+
83
+ **Filters (combinable with AND logic):**
84
+
85
+ | Filter | Description |
86
+ |--------|-------------|
87
+ | `--all` | Run every test suite in the `tests/` directory |
88
+ | `--type <type>` | Filter by suite type: `ui`, `api`, or `audit` |
89
+ | `--suite <name>` | Run suites whose name contains `<name>` (case-insensitive partial match) |
90
+ | `--tag <tag>` | Run all cases that have the given tag |
91
+ | `--test <name>` | Run cases whose name contains `<name>` (case-insensitive partial match) |
92
+
93
+ **Options:**
94
+
95
+ | Option | Description |
96
+ |--------|-------------|
97
+ | `--browser <list...>` | Browser(s) to use: `chromium`, `firefox`, `webkit` (default: from config) |
98
+ | `--device <name>` | Emulate a real device using Playwright's device descriptor (see Device Emulation below) |
99
+ | `--env <name>` | Environment to use: `dev`, `staging`, `prod` (loads matching variable file) |
100
+ | `--parallel <n>` | Number of parallel workers (overrides config) |
101
+ | `--headed` | Run in headed (visible) mode instead of headless |
102
+ | `--ci` | CI mode — no interactive prompts, exit code reflects pass/fail |
103
+ | `--reporter <list...>` | Reporter(s): `allure`, `html`, `json` |
104
+ | `--no-healing` | Disable self-healing for this run |
105
+
106
+ **Examples:**
107
+
108
+ ```bash
109
+ # Run everything
110
+ npx assuremind run --all
111
+
112
+ # Filter by suite type
113
+ npx assuremind run --type ui
114
+ npx assuremind run --type api
115
+ npx assuremind run --type audit
116
+
117
+ # Run the Login Tests suite on two browsers
118
+ npx assuremind run --suite "Login Tests" --browser chromium firefox
119
+
120
+ # Run all smoke tests in headed mode
121
+ npx assuremind run --tag smoke --headed
122
+
123
+ # Run a specific test by name (partial match)
124
+ npx assuremind run --test "Login with valid credentials"
125
+
126
+ # Combinations (filters use AND logic)
127
+ npx assuremind run --type audit --tag regression
128
+ npx assuremind run --suite "Orange HRM" --tag smoke
129
+ npx assuremind run --type audit --test "Login Page"
130
+
131
+ # Device emulation — mobile/tablet testing
132
+ npx assuremind run --all --device "iPhone 15 Pro" --browser chromium
133
+ npx assuremind run --tag smoke --device "Pixel 7" --browser chromium
134
+ npx assuremind run --all --device "iPad Pro 11" --browser webkit
135
+ npx assuremind run --suite "Mobile Checkout" --device "Galaxy S9+" --browser chromium --ci
136
+
137
+ # With additional options
138
+ npx assuremind run --type audit --browser chromium --headed
139
+ npx assuremind run --tag smoke --parallel 4
140
+
141
+ # CI pipeline — exit 1 if any test fails
142
+ npx assuremind run --all --ci
143
+
144
+ # Run against staging with more parallelism
145
+ npx assuremind run --all --env staging --parallel 4
146
+
147
+ # Run without self-healing (faster, useful for debugging)
148
+ npx assuremind run --all --no-healing
149
+ ```
150
+
151
+ ### Device Emulation (`--device`)
152
+
153
+ The `--device` flag emulates a real device using Playwright's built-in device registry. It applies the correct viewport, user-agent, device pixel ratio, `isMobile`, and touch-event flags automatically.
154
+
155
+ **Device name must be an exact Playwright device descriptor name (case-sensitive).**
156
+
157
+ | Category | Device Name | Viewport | DPR | Best Browser |
158
+ |----------|-------------|----------|-----|-------------|
159
+ | Mobile | `iPhone 15 Pro` | 393×852 | 3x | `webkit` |
160
+ | Mobile | `iPhone 15` | 390×844 | 3x | `webkit` |
161
+ | Mobile | `iPhone 14` | 390×844 | 3x | `webkit` |
162
+ | Mobile | `iPhone SE` | 375×667 | 2x | `webkit` |
163
+ | Mobile | `Pixel 7` | 412×915 | 2.6x | `chromium` |
164
+ | Mobile | `Pixel 5` | 393×851 | 2.75x | `chromium` |
165
+ | Mobile | `Galaxy S9+` | 320×658 | 4.5x | `chromium` |
166
+ | Mobile | `Galaxy S8` | 360×740 | 3x | `chromium` |
167
+ | Tablet | `iPad Pro 11` | 834×1194 | 2x | `webkit` |
168
+ | Tablet | `iPad (gen 7)` | 810×1080 | 2x | `webkit` |
169
+ | Tablet | `iPad Mini` | 768×1024 | 2x | `webkit` |
170
+ | Tablet | `Galaxy Tab S4` | 712×1138 | 2.25x | `chromium` |
171
+
172
+ > **Note:** Mobile/tablet emulation works best with `--browser chromium`. Full touch-event support and mobile user-agent headers are only guaranteed on Chromium.
173
+
174
+ ---
175
+
176
+ ## `generate`
177
+
178
+ Generate test cases from a plain-English user story.
179
+
180
+ ```bash
181
+ npx assuremind generate [options]
182
+ ```
183
+
184
+ **Exactly one of `--story` or `--story-file` is required:**
185
+
186
+ | Option | Description |
187
+ |--------|-------------|
188
+ | `--story <text>` | User story text inline |
189
+ | `--story-file <path>` | Path to a `.txt` or `.md` file containing the story |
190
+ | `--suite <name>` | Name for the generated suite (AI chooses a name if omitted) |
191
+ | `--output <path>` | Output directory (default: `tests/`) |
192
+
193
+ **Examples:**
194
+
195
+ ```bash
196
+ # Inline story
197
+ npx assuremind generate \
198
+ --story "As a user I want to reset my password via email so I can regain access." \
199
+ --suite "Password Reset"
200
+
201
+ # Story from file
202
+ npx assuremind generate --story-file stories/checkout.md --suite "Checkout Flow"
203
+
204
+ # Custom output directory
205
+ npx assuremind generate --story-file stories/login.md --output e2e/tests/
206
+ ```
207
+
208
+ The command outputs the generated suite structure and the number of cases and steps created.
209
+
210
+ ---
211
+
212
+ ## `apply-healing`
213
+
214
+ Review and apply self-healing suggestions to test files.
215
+
216
+ ```bash
217
+ npx assuremind apply-healing [options]
218
+ ```
219
+
220
+ | Option | Description |
221
+ |--------|-------------|
222
+ | `--from <path>` | Path to a specific healing report JSON (default: `results/healing/pending.json`) |
223
+ | `-y, --yes` | Accept all pending suggestions without interactive prompting |
224
+
225
+ **Interactive mode (default):**
226
+
227
+ For each pending suggestion you are shown:
228
+
229
+ ```
230
+ Suite: Login Tests → Case: User can log in → Step: step-2
231
+ Instruction: Click the Login button
232
+ Original: await page.click('#login-btn');
233
+ Healed: await page.click('button[type="submit"]');
234
+
235
+ [y] Accept [n] Reject [a] Accept all [q] Quit
236
+ ```
237
+
238
+ **Examples:**
239
+
240
+ ```bash
241
+ # Interactive review
242
+ npx assuremind apply-healing
243
+
244
+ # Accept everything without prompting
245
+ npx assuremind apply-healing --yes
246
+
247
+ # Apply from a CI-generated healing report
248
+ npx assuremind apply-healing --from results/healing/run-2026-03-23.json
249
+ ```
250
+
251
+ ---
252
+
253
+ ## `validate`
254
+
255
+ Validate the config file, environment variables, and test file structure.
256
+
257
+ ```bash
258
+ npx assuremind validate
259
+ ```
260
+
261
+ Checks:
262
+ - `autotest.config.ts` / `autotest.config.json` is valid
263
+ - Required env vars are set (based on `AI_PROVIDER` in `.env`)
264
+ - All `*.test.json` files can be parsed
265
+ - No duplicate suite or case names
266
+
267
+ ---
268
+
269
+ ## `doctor`
270
+
271
+ Check system requirements and configuration health.
272
+
273
+ ```bash
274
+ npx assuremind doctor
275
+ ```
276
+
277
+ Reports:
278
+ - Node.js version
279
+ - Playwright browser installation status
280
+ - `.env` presence and AI provider configuration
281
+ - Network connectivity to the configured AI provider
282
+ - `autotest.config.ts` validity
283
+
284
+ ---
285
+
286
+ ## Exit Codes
287
+
288
+ | Code | Meaning |
289
+ |------|---------|
290
+ | `0` | Success |
291
+ | `1` | Test failures, validation errors, or missing configuration |
292
+
293
+ In non-CI mode, exit code is always `0` unless there is a fatal startup error. Use `--ci` to make the exit code reflect test pass/fail.
294
+
295
+ ---
296
+
297
+ ## Environment Variables Reference
298
+
299
+ All variables are read from `.env` in the project root. See `.env.example` for the full list. Key variables:
300
+
301
+ | Variable | Required | Description |
302
+ |----------|----------|-------------|
303
+ | `AI_PROVIDER` | Yes | Provider name: `anthropic`, `openai`, `google`, `groq`, `deepseek`, `together`, `perplexity`, `qwen`, `bedrock`, `ollama`, `custom` |
304
+ | `ANTHROPIC_API_KEY` | If `anthropic` | Your Anthropic API key |
305
+ | `OPENAI_API_KEY` | If `openai` | Your OpenAI API key |
306
+ | `GOOGLE_API_KEY` | If `google` | Your Google AI Studio key |
307
+ | `GROQ_API_KEY` | If `groq` | Your Groq API key |
308
+ | `AI_TIERED_ENABLED` | No | `true` to use cheap model for simple steps |
309
+ | `AI_TIERED_FAST_PROVIDER` | If tiered | Provider for simple steps |
310
+ | `AI_TIERED_FAST_MODEL` | If tiered | Model for simple steps |
311
+ | `AI_TIMEOUT` | No | AI request timeout in seconds (default: 30) |
312
+ | `AI_MAX_RETRIES` | No | Retry attempts on AI failure (default: 2) |