@_deep4wee/agent-lens 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -31
- package/dist/cli.d.mts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +1169 -957
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1268 -974
- package/dist/cli.mjs.map +1 -1
- package/dist/dsl-BIjVN1M0.d.mts +204 -0
- package/dist/dsl-BIjVN1M0.d.ts +204 -0
- package/dist/index.d.mts +131 -155
- package/dist/index.d.ts +131 -155
- package/dist/index.js +1506 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1479 -3
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/a11y-tree/index.d.mts +11 -0
- package/dist/plugins/a11y-tree/index.d.ts +11 -0
- package/dist/plugins/a11y-tree/index.js +190 -0
- package/dist/plugins/a11y-tree/index.js.map +1 -0
- package/dist/plugins/a11y-tree/index.mjs +155 -0
- package/dist/plugins/a11y-tree/index.mjs.map +1 -0
- package/dist/plugins/desktop-webview2/index.d.mts +14 -0
- package/dist/plugins/desktop-webview2/index.d.ts +14 -0
- package/dist/plugins/desktop-webview2/index.js +258 -0
- package/dist/plugins/desktop-webview2/index.js.map +1 -0
- package/dist/plugins/desktop-webview2/index.mjs +221 -0
- package/dist/plugins/desktop-webview2/index.mjs.map +1 -0
- package/dist/plugins/live-controller/index.d.mts +35 -0
- package/dist/plugins/live-controller/index.d.ts +35 -0
- package/dist/plugins/live-controller/index.js +303 -0
- package/dist/plugins/live-controller/index.js.map +1 -0
- package/dist/plugins/live-controller/index.mjs +261 -0
- package/dist/plugins/live-controller/index.mjs.map +1 -0
- package/dist/plugins/mock-ipc/index.d.mts +30 -0
- package/dist/plugins/mock-ipc/index.d.ts +30 -0
- package/dist/plugins/mock-ipc/index.js +210 -0
- package/dist/plugins/mock-ipc/index.js.map +1 -0
- package/dist/plugins/mock-ipc/index.mjs +181 -0
- package/dist/plugins/mock-ipc/index.mjs.map +1 -0
- package/dist/plugins/visual-diff/index.d.mts +30 -0
- package/dist/plugins/visual-diff/index.d.ts +30 -0
- package/dist/plugins/visual-diff/index.js +163 -0
- package/dist/plugins/visual-diff/index.js.map +1 -0
- package/dist/plugins/visual-diff/index.mjs +127 -0
- package/dist/plugins/visual-diff/index.mjs.map +1 -0
- package/docs/plugins.md +415 -0
- package/package.json +40 -2
- package/skills/agent-lens/SKILL.md +142 -37
- package/skills/agent-lens/examples/06-state-testing-with-mock-ipc.md +13 -12
- package/skills/agent-lens/examples/07-live-controller-interactive-loop.md +108 -0
- package/skills/agent-lens/examples/08-accessibility-semantic-inspection.md +80 -0
- package/skills/agent-lens/examples/09-visual-regression-and-pixel-diffing.md +66 -0
- package/skills/agent-lens/examples/10-authoring-custom-agent-plugins.md +85 -0
- package/skills/agent-lens/references/plugin-development.md +165 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/api/dsl.ts"],"sourcesContent":["import type { Page, BrowserContext } from 'playwright';\nimport type { ConsoleEntry } from '../../features/console-tracker/consoleTracker';\nimport type { MockIpcResponseType } from '../../features/mock-ipc/mockIpc';\n\nexport interface ViewportPreset {\n name: string;\n width: number;\n height: number;\n}\n\nexport const VIEWPORT_PRESETS = {\n /** Minimum supported window size (e.g., ) */\n MIN_SUPPORTED: { name: 'min-supported', width: 1024, height: 768 } as ViewportPreset,\n /** Standard default window size (e.g., ) */\n DEFAULT: { name: 'default', width: 1200, height: 800 } as ViewportPreset,\n /** Wide screen for checking grids and tables */\n WIDE: { name: 'wide', width: 1600, height: 900 } as ViewportPreset,\n /** Full HD */\n FULL_HD: { name: 'full-hd', width: 1920, height: 1080 } as ViewportPreset,\n};\n\nexport interface CaptureOptions {\n selector?: string;\n fullPage?: boolean;\n mask?: string[];\n}\n\nexport interface CaptureBurstOptions {\n /** Total duration of burst animation capture in milliseconds */\n durationMs: number;\n /** Interval between frames in milliseconds (default: 80ms) */\n intervalMs?: number;\n /** Restrict capture to a specific element */\n selector?: string;\n}\n\nexport interface SnapshotMetadata {\n index: number;\n name: string;\n fileName: string;\n filePath: string;\n relativeUri: string;\n viewport: { width: number; height: number };\n timestamp: string;\n isBurstFrame?: boolean;\n burstGroup?: string;\n frameIndex?: number;\n selector?: string;\n}\n\nexport interface TestContext {\n page: Page;\n context: BrowserContext;\n targetMode: 'desktop' | 'preview';\n currentViewport: { width: number; height: number };\n \n // --- Snapshots ---\n\n /** Capture a single high-quality snapshot of the window or element */\n capture: (name: string, options?: CaptureOptions) => Promise<SnapshotMetadata>;\n \n /** Capture a series of frames for animations (Framer Motion, modals, lists) */\n captureBurst: (name: string, options: CaptureBurstOptions) => Promise<SnapshotMetadata[]>;\n \n // --- Navigation ---\n\n /** \n * Dynamically navigate to another page during the test.\n * Example: await ctx.navigate('/settings');\n */\n navigate: (route: string) => Promise<void>;\n\n // --- Viewport ---\n\n /** Change window / viewport size */\n resize: (width: number, height: number) => Promise<void>;\n \n /** Set one of the standard presets (MIN_SUPPORTED, DEFAULT, WIDE) */\n setPreset: (preset: ViewportPreset) => Promise<void>;\n\n /** \n * Dynamically resize the window so it perfectly fits the specified element (or body).\n * Useful when an agent wants to test an isolated component without background clutter.\n */\n resizeToFit: (selector?: string, padding?: number) => Promise<void>;\n \n // --- Waiting ---\n\n /** Wait the specified amount of milliseconds */\n wait: (ms: number) => Promise<void>;\n \n /** Wait for the selector to appear in the DOM */\n waitForSelector: (selector: string, timeoutMs?: number) => Promise<void>;\n \n // --- Interaction ---\n\n /** Left-click the element */\n click: (selector: string) => Promise<void>;\n \n /** Right-click the element (Context Menu) */\n rightClick: (selector: string) => Promise<void>;\n \n /** Type text into an input field */\n type: (selector: string, text: string) => Promise<void>;\n \n /** Select an option in a <select> by its value */\n selectOption: (selector: string, value: string) => Promise<void>;\n \n /** Hover the cursor to check highlights / tooltips */\n hover: (selector: string) => Promise<void>;\n \n /** Scroll the element (or window) down by the specified number of pixels */\n scroll: (selector: string, deltaY: number) => Promise<void>;\n \n // --- Logging ---\n\n /** Log a step for the agent */\n log: (message: string) => void;\n\n // --- Mock IPC (preview mode only) ---\n\n /**\n * Set a mock response for an IPC action.\n * Changes the data returned by window.external.sendMessage in preview mode.\n * Ignored in desktop mode (IPC goes through real backend).\n *\n * Example:\n * await ctx.setMockIpc('GET_INSTANCES', [{ id: '...', name: 'Test' }]);\n * await ctx.setMockIpc('CREATE_INSTANCE', 'Some error', { type: 'ERROR' });\n */\n setMockIpc: (action: string, data: any, options?: { type?: MockIpcResponseType; delayMs?: number }) => Promise<void>;\n\n // --- Console Errors ---\n\n /** Return all intercepted console.error and pageerror logs */\n getConsoleErrors: () => ConsoleEntry[];\n\n /** Return all intercepted console.warn logs */\n getConsoleWarnings: () => ConsoleEntry[];\n\n /** Returns true if there are critical errors in the console */\n hasConsoleErrors: () => boolean;\n\n // --- DOM Assertions ---\n\n /** Get the visible text of a specific element */\n readText: (selector: string) => Promise<string | null>;\n \n /** Get ALL visible text on the page (useful for quick analysis without Vision) */\n getPageText: () => Promise<string>;\n \n /** Check if the element is visible on the screen */\n isVisible: (selector: string) => Promise<boolean>;\n\n /** Count the number of elements matching the selector */\n getElementCount: (selector: string) => Promise<number>;\n}\n\nexport interface VisualScenario {\n /** Unique scenario identifier (kebab-case) */\n id: string;\n /** Human-readable title for the report */\n title: string;\n /** Description of what is being tested */\n description?: string;\n /** Initial route to navigate to (e.g., '/instances', '/settings') */\n route?: string;\n /** List of viewport sizes to test */\n viewports?: ViewportPreset[];\n /**\n * Initial IPC mock data for preview mode.\n * Applied BEFORE navigation to the route.\n */\n mockIpc?: Array<{ action: string; data: any; type?: MockIpcResponseType; delayMs?: number }>;\n /**\n * Optional setup hook: executed before the browser navigates and scenario runs.\n * Ideal for preparing mock files, test directories, or initial state.\n */\n setup?: () => Promise<void> | void;\n /** The main body of the scenario */\n run: (ctx: TestContext) => Promise<void>;\n /**\n * Optional teardown hook: guaranteed to execute in finally, even if run() fails.\n * Ideal for cleaning up test artifacts, cache directories, or temporary state.\n */\n teardown?: () => Promise<void> | void;\n}\n\nexport function defineVisualTest(scenario: VisualScenario): VisualScenario {\n return scenario;\n}\n"],"mappings":";AAUO,IAAM,mBAAmB;AAAA;AAAA,EAE9B,eAAe,EAAE,MAAM,iBAAiB,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAEjE,SAAS,EAAE,MAAM,WAAW,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAErD,MAAM,EAAE,MAAM,QAAQ,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAE/C,SAAS,EAAE,MAAM,WAAW,OAAO,MAAM,QAAQ,KAAK;AACxD;AAyKO,SAAS,iBAAiB,UAA0C;AACzE,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/api/plugin.ts","../src/shared/api/dsl.ts","../src/features/runner/runner.ts","../src/features/capture/capture.ts","../src/features/reporter/reporter.ts","../src/features/console-tracker/consoleTracker.ts","../src/shared/drivers/previewDriver.ts","../src/shared/lib/playwrightLoader.ts","../src/shared/lib/config.ts","../src/shared/lib/processManager.ts","../src/shared/lib/pluginLoader.ts","../src/features/runner/lib/navigation.ts","../src/features/runner/lib/artifactsSync.ts","../src/features/runner/lib/contextBuilder.ts","../src/features/snap/snap.ts"],"sourcesContent":["import type { Page, BrowserContext, Browser } from 'playwright';\nimport type { TestContext, VisualScenario } from './dsl';\nimport type { ReportData } from '../types/report';\n\nexport interface DriverLaunchResult {\n page: Page;\n context: BrowserContext;\n browser?: Browser;\n stop?: () => Promise<void>;\n}\n\nexport interface PluginHookContext {\n scenario?: VisualScenario;\n targetMode: 'desktop' | 'preview' | string;\n artifactsDir: string;\n cliOptions?: Record<string, unknown>;\n state: Map<string, unknown>;\n}\n\nexport interface AgentLensPlugin {\n /** Unique plugin identifier (e.g. 'desktop-webview2', 'live-controller', 'mock-ipc') */\n name: string;\n version?: string;\n\n /** Lifecycle hook: initial setup before drivers and scenarios start */\n setup?: (context: PluginHookContext) => Promise<void> | void;\n\n /**\n * Microkernel driver hook: allows a plugin to provide a custom browser/page session\n * (e.g., desktop WebView2 CDP connection, custom remote browser).\n */\n launchSession?: (\n options: { currentViewport: { width: number; height: number }; headed?: boolean },\n hookContext: PluginHookContext\n ) => Promise<DriverLaunchResult | undefined>;\n\n /** Lifecycle hook: called when browser context is ready */\n onContextCreated?: (context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: called when the test page is created/navigated */\n onPageCreated?: (page: Page, context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /**\n * Context extension: inject custom methods or properties directly into TestContext (ctx)\n */\n extendContext?: (\n ctx: TestContext,\n page: Page,\n hookContext: PluginHookContext\n ) => Record<string, any> | Promise<Record<string, any>>;\n\n /** Lifecycle hook: called after scenario runs and report data is calculated */\n onAfterRun?: (reportData: ReportData, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: teardown and cleanup guaranteed to run */\n teardown?: (hookContext: PluginHookContext) => Promise<void> | void;\n}\n\nexport function definePlugin(plugin: AgentLensPlugin): AgentLensPlugin {\n return plugin;\n}","import type { Page, BrowserContext } from 'playwright';\nimport type { ConsoleEntry } from '../types/console';\nimport type { MockIpcResponseType } from '../types/ipc';\nimport type { AgentLensPlugin } from './plugin';\n\nexport * from './plugin';\nexport * from '../types/console';\nexport * from '../types/ipc';\n\nexport interface ViewportPreset {\n name: string;\n width: number;\n height: number;\n}\n\nexport const VIEWPORT_PRESETS = {\n /** Minimum supported window size */\n MIN_SUPPORTED: { name: 'min-supported', width: 1024, height: 768 } as ViewportPreset,\n /** Standard default window size */\n DEFAULT: { name: 'default', width: 1200, height: 800 } as ViewportPreset,\n /** Wide screen for checking grids and tables */\n WIDE: { name: 'wide', width: 1600, height: 900 } as ViewportPreset,\n /** Full HD */\n FULL_HD: { name: 'full-hd', width: 1920, height: 1080 } as ViewportPreset,\n};\n\nexport interface MockRouteEntry {\n url: string;\n method?: string;\n status?: number;\n body: any;\n delayMs?: number;\n headers?: Record<string, string>;\n}\n\nexport interface CaptureOptions {\n selector?: string;\n fullPage?: boolean;\n mask?: string[];\n}\n\nexport interface CaptureBurstOptions {\n /** Total duration of burst animation capture in milliseconds */\n durationMs: number;\n /** Interval between frames in milliseconds (default: 80ms) */\n intervalMs?: number;\n /** Restrict capture to a specific element */\n selector?: string;\n}\n\nexport interface SnapshotMetadata {\n index: number;\n name: string;\n fileName: string;\n filePath: string;\n relativeUri: string;\n viewport: { width: number; height: number };\n timestamp: string;\n isBurstFrame?: boolean;\n burstGroup?: string;\n frameIndex?: number;\n selector?: string;\n}\n\nexport interface TestContext {\n [key: string]: any;\n page: Page;\n context: BrowserContext;\n targetMode: 'desktop' | 'preview';\n currentViewport: { width: number; height: number };\n\n // --- Snapshots ---\n capture: (name: string, options?: CaptureOptions) => Promise<SnapshotMetadata>;\n captureBurst: (name: string, options: CaptureBurstOptions) => Promise<SnapshotMetadata[]>;\n\n // --- Navigation ---\n navigate: (route: string) => Promise<void>;\n\n // --- Viewport ---\n resize: (width: number, height: number) => Promise<void>;\n setPreset: (preset: ViewportPreset) => Promise<void>;\n resizeToFit: (selector?: string, padding?: number) => Promise<void>;\n\n // --- Waiting ---\n wait: (ms: number) => Promise<void>;\n waitForSelector: (selector: string, timeoutMs?: number) => Promise<void>;\n\n // --- Interaction ---\n click: (selector: string) => Promise<void>;\n rightClick: (selector: string) => Promise<void>;\n type: (selector: string, text: string) => Promise<void>;\n selectOption: (selector: string, value: string) => Promise<void>;\n hover: (selector: string) => Promise<void>;\n scroll: (selector: string, deltaY: number) => Promise<void>;\n\n // --- Logging ---\n log: (message: string) => void;\n\n // --- Mock IPC & Network Routes ---\n setMockIpc: (action: string, data: any, options?: { type?: MockIpcResponseType; delayMs?: number }) => Promise<void>;\n setMockRoute: (\n url: string,\n body: any,\n options?: { method?: string; status?: number; delayMs?: number; headers?: Record<string, string> }\n ) => Promise<void>;\n\n // --- Console Errors ---\n getConsoleErrors: () => ConsoleEntry[];\n getConsoleWarnings: () => ConsoleEntry[];\n hasConsoleErrors: () => boolean;\n\n // --- DOM Assertions ---\n readText: (selector: string) => Promise<string | null>;\n getPageText: () => Promise<string>;\n isVisible: (selector: string) => Promise<boolean>;\n getElementCount: (selector: string) => Promise<number>;\n}\n\nexport interface VisualScenario {\n /** Unique scenario identifier (kebab-case) */\n id: string;\n /** Human-readable title for the report */\n title: string;\n /** Description of what is being tested */\n description?: string;\n /** Initial route to navigate to (e.g., '/instances', '/settings') */\n route?: string;\n /** List of viewport sizes to test */\n viewports?: ViewportPreset[];\n /** Initial IPC mock data for preview mode */\n mockIpc?: Array<{ action: string; data: any; type?: MockIpcResponseType; delayMs?: number }>;\n /** Initial HTTP REST/GraphQL route mocks for preview mode */\n mockRoutes?: MockRouteEntry[];\n /** Optional plugins required for this scenario */\n plugins?: (string | AgentLensPlugin)[];\n /** Setup hook executed before scenario runs */\n setup?: () => Promise<void> | void;\n /** The main body of the scenario */\n run: (ctx: TestContext) => Promise<void>;\n /** Teardown hook guaranteed to run on completion */\n teardown?: () => Promise<void> | void;\n}\n\nexport function defineVisualTest(scenario: VisualScenario): VisualScenario {\n return scenario;\n}\n","import path from 'path';\nimport fs from 'fs';\nimport type { Page, BrowserContext } from 'playwright';\nimport {\n type VisualScenario,\n VIEWPORT_PRESETS,\n type AgentLensPlugin\n} from '../../shared/api/dsl';\nimport { CaptureEngine } from '../capture/capture';\nimport { VisualReporter } from '../reporter/reporter';\nimport { ConsoleTracker } from '../console-tracker/consoleTracker';\nimport { PreviewDriver } from '../../shared/drivers/previewDriver';\nimport { ProcessManager } from '../../shared/lib/processManager';\nimport { PluginManager } from '../../shared/lib/pluginLoader';\nimport { createNavigator } from './lib/navigation';\nimport { syncLatestArtifacts } from './lib/artifactsSync';\nimport { buildTestContext } from './lib/contextBuilder';\n\nexport interface RunOptions {\n scenario: VisualScenario;\n targetMode?: 'desktop' | 'preview' | string;\n artifactsRoot?: string;\n autoLaunchDesktop?: boolean;\n port?: number;\n wwwrootDir?: string;\n executablePath?: string;\n desktopArgs?: string[];\n desktopEnv?: Record<string, string>;\n cleanPaths?: string[];\n url?: string;\n startCommand?: string;\n startCwd?: string;\n cleanArtifacts?: boolean;\n headed?: boolean;\n detach?: boolean;\n globalMocks?: any[];\n plugins?: (string | AgentLensPlugin)[];\n}\n\nexport interface RunResult {\n scenarioId: string;\n targetMode: 'desktop' | 'preview' | string;\n success: boolean;\n totalSnapshots: number;\n consoleErrors: number;\n consoleWarnings: number;\n reportPath: string;\n artifactsDir: string;\n error?: string;\n}\n\nexport async function runVisualScenario(options: RunOptions): Promise<RunResult> {\n const { scenario } = options;\n const targetMode = options.targetMode || 'preview';\n const startTime = Date.now();\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const artifactsRoot = options.artifactsRoot || path.resolve(process.cwd(), 'artifacts');\n\n if (options.cleanArtifacts && fs.existsSync(artifactsRoot)) {\n console.log(`🧹 [Clean Artifacts] Purging previous artifact runs in ${artifactsRoot}...`);\n const entries = fs.readdirSync(artifactsRoot, { withFileTypes: true });\n for (const entry of entries) {\n fs.rmSync(path.join(artifactsRoot, entry.name), { recursive: true, force: true });\n }\n }\n\n const scenarioArtifactsDir = path.join(artifactsRoot, `${scenario.id}_${timestamp}`);\n if (!fs.existsSync(scenarioArtifactsDir)) {\n fs.mkdirSync(scenarioArtifactsDir, { recursive: true });\n }\n\n const captureEngine = new CaptureEngine(scenarioArtifactsDir);\n const consoleTracker = new ConsoleTracker();\n const defaultViewport = scenario.viewports?.[0] || VIEWPORT_PRESETS.DEFAULT;\n const currentViewport = { width: defaultViewport.width, height: defaultViewport.height };\n\n let previewDriver: PreviewDriver | null = null;\n let processManager: ProcessManager | null = null;\n let customDriverStop: (() => Promise<void>) | null = null;\n\n // Initialize and load plugins\n const pluginManager = new PluginManager();\n const pluginSpecs = [...(options.plugins || []), ...(scenario.plugins || [])];\n if (pluginSpecs.length > 0) {\n await pluginManager.loadAll(pluginSpecs);\n }\n\n const hookContext = {\n scenario,\n targetMode,\n artifactsDir: scenarioArtifactsDir,\n cliOptions: options as unknown as Record<string, unknown>,\n state: new Map<string, unknown>()\n };\n\n try {\n let page: Page;\n let context: BrowserContext;\n\n console.log(`\\n========================================`);\n console.log(`🚀 Starting Visual Test: ${scenario.title}`);\n console.log(`🎯 Mode: ${targetMode.toUpperCase()}`);\n console.log(`📁 Artifacts: ${scenarioArtifactsDir}`);\n console.log(`========================================\\n`);\n\n // 1. Optional dev-server/process management\n if (options.startCommand) {\n processManager = new ProcessManager();\n await processManager.start(options.startCommand, { cwd: options.startCwd });\n const waitTarget = options.url || 'http://localhost:5173';\n await processManager.waitForUrl(waitTarget);\n }\n\n // 2. Run plugin setup hooks\n await pluginManager.runSetup(hookContext);\n\n if (typeof scenario.setup === 'function') {\n console.log(`🔧 [Scenario Setup] Executing setup hook...`);\n await scenario.setup();\n }\n\n // 3. Driver Session Launch (Microkernel Pattern)\n const customSession = await pluginManager.launchSession(\n { currentViewport, headed: options.headed },\n hookContext\n );\n\n if (customSession) {\n page = customSession.page;\n context = customSession.context;\n if (customSession.stop) {\n customDriverStop = customSession.stop;\n }\n } else if (targetMode === 'desktop') {\n throw new Error(\n `[AgentLens] Target mode is 'desktop', but no desktop plugin is registered. ` +\n `Please add --plugin=desktop-webview2 to your command or configuration.`\n );\n } else {\n // Standard Core Preview Driver (Chromium)\n previewDriver = new PreviewDriver({\n wwwrootDir: options.wwwrootDir,\n url: options.url,\n headed: options.headed\n });\n\n if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {\n console.log(`🌐 [Mock Network] Queuing ${scenario.mockRoutes.length} route mock(s)`);\n await previewDriver.setupRouteMocks(scenario.mockRoutes);\n }\n\n const res = await previewDriver.start(currentViewport);\n page = res.page;\n context = res.context;\n\n if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {\n await previewDriver.setupRouteMocks(scenario.mockRoutes);\n }\n }\n\n // 4. Run plugin browser/context lifecycle hooks\n await pluginManager.runOnContextCreated(context, hookContext);\n await pluginManager.runOnPageCreated(page, context, hookContext);\n\n // 5. Attach console error interception\n consoleTracker.attach(page);\n console.log(`🔍 [Console Tracker] Attached — errors and warnings will be captured\\n`);\n\n // 6. Navigation and Context Setup\n const doNavigate = createNavigator(page, previewDriver?.baseUrl, options.url);\n\n if (scenario.route) {\n console.log(`🧭 [Runner] Navigating to initial route: ${scenario.route}`);\n await doNavigate(scenario.route);\n }\n\n const { ctx } = buildTestContext({\n page,\n context,\n targetMode,\n initialViewport: currentViewport,\n captureEngine,\n consoleTracker,\n previewDriver,\n doNavigate\n });\n\n // 7. Allow plugins to extend TestContext (e.g. mock-ipc, live-controller)\n await pluginManager.extendContext(ctx, page, hookContext);\n\n // 8. Execute the scenario body\n await scenario.run(ctx);\n\n const durationMs = Date.now() - startTime;\n const snapshots = captureEngine.getSnapshots();\n const consoleErrors = consoleTracker.getErrors();\n const consoleWarnings = consoleTracker.getWarnings();\n\n if (consoleErrors.length > 0) {\n console.log(`\\n🔴 Console Errors: ${consoleErrors.length}`);\n consoleErrors.forEach((err, i) => console.log(` ${i + 1}. ${err.text.slice(0, 120)}`));\n }\n if (consoleWarnings.length > 0) {\n console.log(`\\n🟡 Console Warnings: ${consoleWarnings.length}`);\n }\n\n // 9. Generate Report & Run AfterRun Hooks\n const reportData = {\n scenario,\n snapshots,\n consoleErrors,\n consoleWarnings,\n outputDir: scenarioArtifactsDir,\n targetMode,\n durationMs,\n customSections: []\n };\n\n await pluginManager.runOnAfterRun(reportData, hookContext);\n const reportPath = VisualReporter.generateReport(reportData);\n const latestReport = syncLatestArtifacts(artifactsRoot, scenarioArtifactsDir, reportPath, snapshots);\n\n console.log(`\\n✅ Visual Test Completed Successfully!`);\n console.log(`📊 Captured Snapshots: ${snapshots.length}`);\n console.log(`🔴 Console Errors: ${consoleErrors.length}`);\n console.log(`🟡 Console Warnings: ${consoleWarnings.length}`);\n console.log(`📄 Markdown Report: ${reportPath}`);\n if (latestReport) {\n console.log(`📌 Latest Report: ${latestReport}`);\n }\n\n return {\n scenarioId: scenario.id,\n targetMode,\n success: true,\n totalSnapshots: snapshots.length,\n consoleErrors: consoleErrors.length,\n consoleWarnings: consoleWarnings.length,\n reportPath,\n artifactsDir: scenarioArtifactsDir\n };\n } catch (err: any) {\n console.error(`\\n❌ Visual Test Failed: ${err.message}`);\n const durationMs = Date.now() - startTime;\n const snapshots = captureEngine.getSnapshots();\n const consoleErrors = consoleTracker.getErrors();\n const consoleWarnings = consoleTracker.getWarnings();\n\n const reportData = {\n scenario,\n snapshots,\n consoleErrors,\n consoleWarnings,\n outputDir: scenarioArtifactsDir,\n targetMode,\n durationMs\n };\n\n const reportPath = VisualReporter.generateReport(reportData);\n syncLatestArtifacts(artifactsRoot, scenarioArtifactsDir, reportPath, snapshots);\n\n return {\n scenarioId: scenario.id,\n targetMode,\n success: false,\n totalSnapshots: snapshots.length,\n consoleErrors: consoleErrors.length,\n consoleWarnings: consoleWarnings.length,\n reportPath,\n artifactsDir: scenarioArtifactsDir,\n error: err.message\n };\n } finally {\n // 10. Guaranteed teardown sequence\n await pluginManager.runTeardown(hookContext);\n\n if (typeof scenario.teardown === 'function') {\n try {\n console.log(`🧹 [Scenario Teardown] Executing teardown hook...`);\n await scenario.teardown();\n } catch (teardownErr: any) {\n console.error(`⚠️ [Scenario Teardown] Error during teardown:`, teardownErr.message);\n }\n }\n\n if (options.cleanPaths && options.cleanPaths.length > 0) {\n for (const cleanTarget of options.cleanPaths) {\n try {\n const resolvedCleanPath = path.resolve(process.cwd(), cleanTarget);\n if (fs.existsSync(resolvedCleanPath)) {\n console.log(`🧹 [Auto-Cleanup] Removing: ${resolvedCleanPath}`);\n fs.rmSync(resolvedCleanPath, { recursive: true, force: true });\n }\n } catch (cleanErr: any) {\n console.error(`⚠️ [Auto-Cleanup] Failed to remove ${cleanTarget}:`, cleanErr.message);\n }\n }\n }\n\n if (!options.detach) {\n if (customDriverStop) await customDriverStop();\n if (previewDriver) await previewDriver.stop();\n if (processManager) await processManager.stop();\n } else {\n console.log('🔗 [Runner] Detach mode active, leaving browser/app open.');\n }\n }\n}","import fs from 'fs';\r\nimport path from 'path';\r\nimport type { Page } from 'playwright';\r\nimport type { CaptureOptions, CaptureBurstOptions, SnapshotMetadata } from '../../shared/api/dsl';\r\n\r\nexport class CaptureEngine {\r\n private outputDir: string;\r\n private currentStepIndex: number = 0;\r\n private recordedSnapshots: SnapshotMetadata[] = [];\r\n\r\n constructor(outputDir: string) {\r\n this.outputDir = outputDir;\r\n if (!fs.existsSync(this.outputDir)) {\r\n fs.mkdirSync(this.outputDir, { recursive: true });\r\n }\r\n }\r\n\r\n public getSnapshots(): SnapshotMetadata[] {\r\n return this.recordedSnapshots;\r\n }\r\n\r\n public getOutputDir(): string {\r\n return this.outputDir;\r\n }\r\n\r\n /**\r\n */\r\n public async takeSnapshot(\r\n page: Page,\r\n name: string,\r\n viewport: { width: number; height: number },\r\n options?: CaptureOptions\r\n ): Promise<SnapshotMetadata> {\r\n this.currentStepIndex += 1;\r\n const paddedIndex = String(this.currentStepIndex).padStart(2, '0');\r\n const sanitizedName = name.replace(/[^a-zA-Z0-9_\\-]/g, '_');\r\n const fileName = `${paddedIndex}_${sanitizedName}_${viewport.width}x${viewport.height}.png`;\r\n const filePath = path.join(this.outputDir, fileName);\r\n\r\n if (options?.selector) {\r\n const element = await page.waitForSelector(options.selector, { timeout: 5000 });\r\n await element.screenshot({ path: filePath });\r\n } else {\r\n await page.screenshot({ path: filePath, fullPage: options?.fullPage ?? false });\r\n }\r\n\r\n const metadata: SnapshotMetadata = {\r\n index: this.currentStepIndex,\r\n name,\r\n fileName,\r\n filePath,\r\n relativeUri: `./${fileName}`,\r\n viewport,\r\n timestamp: new Date().toISOString(),\r\n selector: options?.selector\r\n };\r\n\r\n this.recordedSnapshots.push(metadata);\r\n return metadata;\r\n }\r\n\r\n /**\r\n */\r\n public async takeBurst(\r\n page: Page,\r\n name: string,\r\n viewport: { width: number; height: number },\r\n options: CaptureBurstOptions\r\n ): Promise<SnapshotMetadata[]> {\r\n this.currentStepIndex += 1;\r\n const stepIndex = this.currentStepIndex;\r\n const paddedIndex = String(stepIndex).padStart(2, '0');\r\n const sanitizedName = name.replace(/[^a-zA-Z0-9_\\-]/g, '_');\r\n\r\n const duration = Math.max(options.durationMs, 50);\r\n const interval = Math.max(options.intervalMs ?? 80, 20);\r\n const totalFrames = Math.ceil(duration / interval);\r\n\r\n const burstSnapshots: SnapshotMetadata[] = [];\r\n const burstGroup = sanitizedName;\r\n\r\n for (let frame = 0; frame <= totalFrames; frame++) {\r\n const elapsedMs = frame * interval;\r\n const paddedFrame = String(frame + 1).padStart(2, '0');\r\n const fileName = `${paddedIndex}_burst_${sanitizedName}_f${paddedFrame}_${elapsedMs}ms.png`;\r\n const filePath = path.join(this.outputDir, fileName);\r\n\r\n if (options.selector) {\r\n const element = await page.$(options.selector);\r\n if (element) {\r\n await element.screenshot({ path: filePath });\r\n } else {\r\n await page.screenshot({ path: filePath });\r\n }\r\n } else {\r\n await page.screenshot({ path: filePath });\r\n }\r\n\r\n const meta: SnapshotMetadata = {\r\n index: stepIndex,\r\n name: `${name} (frame ${frame + 1}, +${elapsedMs}ms)`,\r\n fileName,\r\n filePath,\r\n relativeUri: `./${fileName}`,\r\n viewport,\r\n timestamp: new Date().toISOString(),\r\n isBurstFrame: true,\r\n burstGroup,\r\n frameIndex: frame + 1,\r\n selector: options.selector\r\n };\r\n\r\n burstSnapshots.push(meta);\r\n this.recordedSnapshots.push(meta);\r\n\r\n if (frame < totalFrames) {\r\n await new Promise((r) => setTimeout(r, interval));\r\n }\r\n }\r\n\r\n return burstSnapshots;\r\n }\r\n}\r\n","import fs from 'fs';\nimport path from 'path';\nimport type { SnapshotMetadata, VisualScenario } from '../../shared/api/dsl';\nimport type { ConsoleEntry } from '../../shared/types/console';\nimport type { ReportData } from '../../shared/types/report';\n\nexport type { ReportData };\n\n\nexport class VisualReporter {\n public static generateReport(data: ReportData): string {\n const {\n scenario,\n snapshots,\n consoleErrors,\n consoleWarnings,\n outputDir,\n targetMode,\n durationMs\n } = data;\n\n const manifestPath = path.join(outputDir, 'manifest.json');\n const reportPath = path.join(outputDir, 'report.md');\n\n const manifest = {\n scenarioId: scenario.id,\n title: scenario.title,\n description: scenario.description,\n targetMode,\n durationMs,\n totalSnapshots: snapshots.length,\n consoleErrors: consoleErrors.length,\n consoleWarnings: consoleWarnings.length,\n createdAt: new Date().toISOString(),\n snapshots,\n errors: consoleErrors,\n warnings: consoleWarnings\n };\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n const rows: string[] = [];\n \n const regularSnapshots = snapshots.filter((s) => !s.isBurstFrame);\n const burstGroups = new Map<string, SnapshotMetadata[]>();\n\n snapshots.filter((s) => s.isBurstFrame && s.burstGroup).forEach((s) => {\n const list = burstGroups.get(s.burstGroup!) || [];\n list.push(s);\n burstGroups.set(s.burstGroup!, list);\n });\n\n for (const snap of regularSnapshots) {\n const relativeLink = snap.relativeUri || `./${snap.fileName}`;\n rows.push(\n `| **${String(snap.index).padStart(2, '0')}** | \\`${snap.viewport.width}x${snap.viewport.height}\\` | ${snap.name} | [${snap.fileName}](${relativeLink}) |`\n );\n }\n\n let burstSections = '';\n if (burstGroups.size > 0) {\n burstSections += `\\n### 🎬 Animation Bursts\\n\\n`;\n for (const [group, frames] of burstGroups.entries()) {\n burstSections += `#### Animation: \\`${group}\\` (${frames.length} frames)\\n\\n`;\n burstSections += `| Frame | Viewport | File | Preview |\\n| :--- | :--- | :--- | :--- |\\n`;\n for (const frame of frames) {\n const relativeLink = frame.relativeUri || `./${frame.fileName}`;\n burstSections += `| Frame ${frame.frameIndex} | \\`${frame.viewport.width}x${frame.viewport.height}\\` | [${frame.fileName}](${relativeLink}) |  |\\n`;\n }\n burstSections += `\\n`;\n }\n }\n \n\n const healthStatus = this.getHealthStatus(consoleErrors, consoleWarnings);\n let consoleSections = '';\n\n if (consoleErrors.length > 0) {\n consoleSections += `\\n## 🔴 Console Errors (${consoleErrors.length})\\n\\n`;\n consoleSections += `> [!CAUTION]\\n> Found **${consoleErrors.length}** JavaScript errors during the test. This might indicate broken components, missing data, or unhandled exceptions.\\n\\n`;\n consoleSections += `| # | Time | Error | URL |\\n| :-: | :--- | :--- | :--- |\\n`;\n consoleErrors.forEach((err, i) => {\n const time = err.timestamp.split('T')[1]?.slice(0, 8) || '';\n const text = err.text.replace(/\\|/g, '\\\\|').slice(0, 200);\n const url = err.url.replace(/\\|/g, '\\\\|');\n consoleSections += `| ${i + 1} | \\`${time}\\` | ${text} | \\`${url}\\` |\\n`;\n });\n\n const withStack = consoleErrors.filter(e => e.stack);\n if (withStack.length > 0) {\n consoleSections += `\\n<details>\\n<summary>📋 Stack Traces (${withStack.length})</summary>\\n\\n`;\n withStack.forEach((err, i) => {\n consoleSections += `**Error ${i + 1}:** \\`${err.text.slice(0, 100)}\\`\\n\\`\\`\\`\\n${err.stack}\\n\\`\\`\\`\\n\\n`;\n });\n consoleSections += `</details>\\n`;\n }\n }\n\n if (consoleWarnings.length > 0) {\n consoleSections += `\\n## 🟡 Console Warnings (${consoleWarnings.length})\\n\\n`;\n consoleSections += `| # | Warning |\\n| :-: | :--- |\\n`;\n consoleWarnings.slice(0, 20).forEach((warn, i) => {\n const text = warn.text.replace(/\\|/g, '\\\\|').slice(0, 200);\n consoleSections += `| ${i + 1} | ${text} |\\n`;\n });\n if (consoleWarnings.length > 20) {\n consoleSections += `\\n*...and ${consoleWarnings.length - 20} more warnings (full list in manifest.json)*\\n`;\n }\n }\n\n let pluginSections = '';\n if (data.customSections && data.customSections.length > 0) {\n for (const sec of data.customSections) {\n pluginSections += `\\n## ${sec.title}\\n\\n${sec.content}\\n\\n`;\n }\n }\n\n const reportContent = `# 📸 Visual Test Report: ${scenario.title}\n\n> **Scenario ID:** \\`${scenario.id}\\` \n> **Target Mode:** \\`${targetMode.toUpperCase()}\\` (${targetMode === 'desktop' ? 'Native Desktop App / WebView2' : 'Fast Web Preview'}) \n> **Execution Duration:** ${(durationMs / 1000).toFixed(2)} s \n> **Total Snapshots Captured:** ${snapshots.length} \n> **Health Status:** ${healthStatus} \n> **Artifacts Folder:** \\`${outputDir}\\`\n\n---\n\n## 🖼️ Primary Snapshot Overview\n\n| # | Viewport | Step Description | File Reference |\n| :-: | :--- | :--- | :--- |\n${rows.join('\\n')}\n\n${burstSections}\n${consoleSections}\n${pluginSections}\n---\n\n## 📋 AI Agent Verification Checklist:\n- [ ] **Console Errors**: ${consoleErrors.length === 0 ? '✅ No errors found' : `❌ ${consoleErrors.length} errors — MUST REVIEW`}\n- [ ] **Visual Layout Check**: Inspect snapshots (e.g. view_image on \\`./01_${regularSnapshots[0]?.fileName || 'quick_snap'}\\`) for layout shifts or clipped elements.\n- [ ] **Responsiveness at \\`1024x768\\`**: Elements do not overflow the screen, no unwanted horizontal scroll.\n \n- [ ] **Typography & Spacing**: Spacing matches the design system and layout grids.\n- [ ] **Color Palette & Theme**: Background tints and button accent colors match the concept.\n- [ ] **Component States**: Modals open centered, dropdowns do not overlap with other layers (z-index).\n- [ ] **Localization**: Verify that there are no raw i18n keys (text with dots like \\`sidebar.home\\` instead of \"Home\").\n\n`;\n\n fs.writeFileSync(reportPath, reportContent, 'utf-8');\n return reportPath;\n }\n\n private static getHealthStatus(errors: ConsoleEntry[], warnings: ConsoleEntry[]): string {\n if (errors.length === 0 && warnings.length === 0) {\n return '✅ Healthy — no errors or warnings';\n }\n if (errors.length === 0 && warnings.length > 0) {\n return `⚠️ Warnings (${warnings.length}) — no critical errors`;\n }\n return `❌ ERRORS (${errors.length} errors, ${warnings.length} warnings) — requires attention`;\n }\n}\n","import type { Page, ConsoleMessage } from 'playwright';\r\nimport type { ConsoleEntry } from '../../shared/types/console';\r\n\r\nexport type { ConsoleEntry };\r\n\r\n\r\nexport class ConsoleTracker {\r\n private entries: ConsoleEntry[] = [];\r\n private attached = false;\r\n\r\n public attach(page: Page): void {\r\n if (this.attached) return;\r\n this.attached = true;\r\n\r\n page.on('console', (msg: ConsoleMessage) => {\r\n const type = msg.type(); // 'log' | 'error' | 'warning' | 'info' | 'debug' | 'trace' | ...\r\n const level = this.mapConsoleType(type);\r\n\r\n const entry: ConsoleEntry = {\r\n level,\r\n text: msg.text(),\r\n url: page.url(),\r\n timestamp: new Date().toISOString()\r\n };\r\n\r\n this.entries.push(entry);\r\n\r\n if (level === 'error') {\r\n console.log(`🔴 [Console ERROR] ${msg.text()}`);\r\n } else if (level === 'warning') {\r\n if (!this.isIgnoredWarning(msg.text())) {\r\n console.log(`🟡 [Console WARN] ${msg.text()}`);\r\n }\r\n }\r\n });\r\n\r\n page.on('pageerror', (error: Error) => {\r\n const entry: ConsoleEntry = {\r\n level: 'error',\r\n text: error.message,\r\n url: page.url(),\r\n timestamp: new Date().toISOString(),\r\n stack: error.stack\r\n };\r\n\r\n this.entries.push(entry);\r\n console.log(`💥 [Page ERROR] ${error.message}`);\r\n });\r\n }\r\n\r\n public getErrors(): ConsoleEntry[] {\r\n return this.entries.filter(e => e.level === 'error');\r\n }\r\n\r\n public getWarnings(): ConsoleEntry[] {\r\n return this.entries\r\n .filter(e => e.level === 'warning')\r\n .filter(e => !this.isIgnoredWarning(e.text));\r\n }\r\n\r\n public getAll(): ConsoleEntry[] {\r\n return [...this.entries];\r\n }\r\n\r\n public get errorCount(): number {\r\n return this.getErrors().length;\r\n }\r\n\r\n public get warningCount(): number {\r\n return this.getWarnings().length;\r\n }\r\n\r\n public get hasErrors(): boolean {\r\n return this.errorCount > 0;\r\n }\r\n\r\n public clear(): void {\r\n this.entries = [];\r\n }\r\n\r\n\r\n private mapConsoleType(type: string): ConsoleEntry['level'] {\r\n switch (type) {\r\n case 'error': return 'error';\r\n case 'warning': return 'warning';\r\n case 'info': return 'info';\r\n case 'debug': return 'debug';\r\n default: return 'log';\r\n }\r\n }\r\n\r\n\r\n private isIgnoredWarning(text: string): boolean {\r\n const ignoredPatterns = [\r\n 'findDOMNode is deprecated',\r\n // Chromium DevTools\r\n 'DevTools',\r\n // Playwright injection\r\n '__playwright',\r\n '[vite]',\r\n // React 18 hydration warnings\r\n 'Extra attributes from the server',\r\n 'Download the React DevTools',\r\n ];\r\n return ignoredPatterns.some(pattern => text.includes(pattern));\r\n }\r\n}\r\n","import http from 'http';\nimport fs from 'fs';\nimport path from 'path';\nimport type { Browser, BrowserContext, Page } from 'playwright';\nimport { chromium } from '../lib/playwrightLoader';\nimport type { MockRouteEntry } from '../api/dsl';\nimport { resolveWwwrootDir } from '../lib/config';\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf'\n};\n\nexport interface PreviewDriverOptions {\n wwwrootDir?: string;\n headed?: boolean;\n url?: string;\n}\n\nexport class PreviewDriver {\n private server: http.Server | null = null;\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private page: Page | null = null;\n private serverPort: number = 0;\n private options: PreviewDriverOptions;\n private _baseUrl: string = '';\n private initialRouteMocks: MockRouteEntry[] = [];\n\n constructor(options?: PreviewDriverOptions) {\n this.options = options || {};\n if (!this.options.url) {\n this.options.wwwrootDir = resolveWwwrootDir(this.options.wwwrootDir);\n }\n }\n\n public get baseUrl(): string {\n return this._baseUrl;\n }\n\n public async start(initialViewport = { width: 1200, height: 800 }): Promise<{ page: Page; context: BrowserContext; browser: Browser }> {\n let targetUrl = this.options.url;\n\n if (!targetUrl) {\n if (!fs.existsSync(this.options.wwwrootDir!)) {\n throw new Error(`Directory not found at: ${this.options.wwwrootDir}. Please build your frontend project first or pass --url=<url>.`);\n }\n\n await new Promise<void>((resolve, reject) => {\n this.server = http.createServer((req, res) => {\n let reqUrl = req.url?.split('?')[0] || '/';\n if (reqUrl === '/') reqUrl = '/index.html';\n\n let safePath = path.normalize(path.join(this.options.wwwrootDir!, reqUrl));\n if (!safePath.startsWith(this.options.wwwrootDir!)) {\n res.writeHead(403);\n return res.end('Forbidden');\n }\n\n if (!fs.existsSync(safePath) || fs.statSync(safePath).isDirectory()) {\n // SPA fallback to index.html\n safePath = path.join(this.options.wwwrootDir!, 'index.html');\n }\n\n const ext = path.extname(safePath).toLowerCase();\n const contentType = MIME_TYPES[ext] || 'application/octet-stream';\n\n try {\n const content = fs.readFileSync(safePath);\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(content);\n } catch (e: any) {\n res.writeHead(500);\n res.end(`Server error: ${e.message}`);\n }\n });\n\n this.server.listen(0, '127.0.0.1', () => {\n const addr = this.server?.address();\n if (typeof addr === 'object' && addr?.port) {\n this.serverPort = addr.port;\n this._baseUrl = `http://127.0.0.1:${this.serverPort}`;\n resolve();\n } else {\n reject(new Error('Failed to acquire port for static preview server'));\n }\n });\n });\n\n targetUrl = `${this._baseUrl}/index.html`;\n } else {\n this._baseUrl = targetUrl;\n console.log(`🌐 [PreviewDriver] Connecting directly to live URL: ${targetUrl}`);\n }\n\n this.browser = await chromium.launch({\n headless: !this.options.headed,\n args: ['--no-sandbox', '--disable-setuid-sandbox']\n });\n\n this.context = await this.browser.newContext({\n viewport: initialViewport,\n deviceScaleFactor: 1\n });\n\n this.page = await this.context.newPage();\n\n if (this.initialRouteMocks.length > 0) {\n for (const entry of this.initialRouteMocks) {\n await this.addRouteMock(entry);\n }\n }\n\n await this.page.goto(targetUrl, { waitUntil: 'domcontentloaded' });\n\n return { page: this.page, context: this.context, browser: this.browser };\n }\n\n public async addRouteMock(entry: MockRouteEntry): Promise<void> {\n if (!this.page) {\n this.initialRouteMocks.push(entry);\n return;\n }\n\n await this.page.route(entry.url, async (route) => {\n const req = route.request();\n if (entry.method && req.method().toUpperCase() !== entry.method.toUpperCase()) {\n return route.continue();\n }\n\n if (entry.delayMs) {\n await new Promise((r) => setTimeout(r, entry.delayMs));\n }\n\n const isJson = typeof entry.body === 'object' && entry.body !== null;\n await route.fulfill({\n status: entry.status ?? 200,\n contentType: isJson ? 'application/json' : 'text/plain; charset=utf-8',\n body: isJson ? JSON.stringify(entry.body) : String(entry.body ?? ''),\n headers: entry.headers\n });\n });\n }\n\n public async setupRouteMocks(routes: MockRouteEntry[]): Promise<void> {\n for (const r of routes) {\n await this.addRouteMock(r);\n }\n }\n\n public async stop(): Promise<void> {\n if (this.context) {\n await this.context.close().catch(() => {});\n this.context = null;\n }\n if (this.browser) {\n await this.browser.close().catch(() => {});\n this.browser = null;\n }\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server?.close(() => resolve());\n });\n this.server = null;\n }\n }\n}\n","import { chromium as baseChromium, type ChromiumBrowser, type BrowserType } from 'playwright';\r\n\r\n/**\r\n * Safe Playwright Chromium loader that wraps browser initialization with helpful diagnostic messages.\r\n */\r\nexport const chromium: BrowserType<ChromiumBrowser> = new Proxy(baseChromium, {\r\n get(target, prop, receiver) {\r\n if (prop === 'launch') {\r\n return async (...args: Parameters<typeof baseChromium.launch>) => {\r\n try {\r\n return await target.launch(...args);\r\n } catch (err: any) {\r\n if (\r\n err.message?.includes(\"Executable doesn't exist\") ||\r\n err.message?.includes('playwright install') ||\r\n err.message?.includes('browser has not been downloaded')\r\n ) {\r\n console.error(\"\\n❌ [AgentLens] Playwright Chromium browser binary is missing!\");\r\n console.error(\"👉 Please install it by running: npx playwright install chromium\\n\");\r\n }\r\n throw err;\r\n }\r\n };\r\n }\r\n const val = Reflect.get(target, prop, receiver);\r\n return typeof val === 'function' ? val.bind(target) : val;\r\n }\r\n});","import path from 'path';\nimport fs from 'fs';\n\nexport interface AgentLensConfigFile {\n mode?: 'desktop' | 'preview';\n port?: number;\n headed?: boolean;\n detach?: boolean;\n buildCommand?: string;\n autoBuild?: boolean;\n startCommand?: string;\n startCwd?: string;\n cleanArtifacts?: boolean;\n url?: string;\n executablePath?: string;\n clean?: string | string[];\n scenarios?: string;\n wwwroot?: string;\n outDir?: string;\n env?: Record<string, string>;\n plugins?: string[];\n}\n\nexport function loadConfig(cwd: string = process.cwd()): AgentLensConfigFile {\n const jsonConfigPath = path.join(cwd, 'agent-lens.json');\n if (fs.existsSync(jsonConfigPath)) {\n try {\n const raw = fs.readFileSync(jsonConfigPath, 'utf-8');\n return JSON.parse(raw);\n } catch (e: any) {\n console.warn(`⚠️ [Config] Failed to parse agent-lens.json: ${e.message}`);\n }\n }\n\n const packageJsonPath = path.join(cwd, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n try {\n const raw = fs.readFileSync(packageJsonPath, 'utf-8');\n const pkg = JSON.parse(raw);\n if (pkg.agentLens && typeof pkg.agentLens === 'object') {\n return pkg.agentLens;\n }\n } catch {\n // ignore\n }\n }\n\n return {};\n}\n\nexport function detectStartCwd(providedCwd?: string): string | undefined {\n if (providedCwd) {\n const resolved = path.resolve(process.cwd(), providedCwd);\n if (fs.existsSync(resolved)) {\n return providedCwd;\n }\n }\n\n const rootPkgPath = path.join(process.cwd(), 'package.json');\n let rootHasDevScript = false;\n if (fs.existsSync(rootPkgPath)) {\n try {\n const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));\n rootHasDevScript = Boolean(rootPkg.scripts?.dev || rootPkg.scripts?.start);\n } catch {\n // ignore\n }\n }\n\n if (rootHasDevScript) {\n return undefined;\n }\n\n const candidates = ['Frontend', 'frontend', 'client', 'web', 'ui', 'apps/web', 'src/frontend'];\n for (const candidate of candidates) {\n const candidatePkg = path.join(process.cwd(), candidate, 'package.json');\n if (fs.existsSync(candidatePkg)) {\n return `./${candidate}`;\n }\n }\n\n return undefined;\n}\n\nexport function resolveWwwrootDir(customDir?: string): string {\n if (customDir) {\n return path.resolve(process.cwd(), customDir);\n }\n\n const candidates = [\n 'dist',\n 'build',\n 'out',\n 'wwwroot',\n 'Frontend/dist',\n 'frontend/dist',\n 'client/dist'\n ];\n\n for (const c of candidates) {\n const candidatePath = path.resolve(process.cwd(), c);\n if (fs.existsSync(candidatePath) && fs.existsSync(path.join(candidatePath, 'index.html'))) {\n return candidatePath;\n }\n }\n\n for (const c of candidates) {\n const candidatePath = path.resolve(process.cwd(), c);\n if (fs.existsSync(candidatePath)) {\n return candidatePath;\n }\n }\n\n return path.resolve(process.cwd(), 'dist');\n}","import { spawn, type ChildProcess } from 'child_process';\nimport treeKill from 'tree-kill';\n\nexport interface ProcessStartOptions {\n \n cwd?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nexport class ProcessManager {\n private childProcess: ChildProcess | null = null;\n private isStopped = false;\n\n public async start(command: string, options?: ProcessStartOptions): Promise<void> {\n const cwd = options?.cwd || process.cwd();\n console.log(`🚀 [ProcessManager] Starting command: \"${command}\" in ${cwd}`);\n\n this.childProcess = spawn(command, {\n cwd,\n env: { ...process.env, ...options?.env },\n shell: true,\n stdio: ['ignore', 'pipe', 'pipe']\n });\n\n this.childProcess.stdout?.on('data', (chunk) => {\n const line = chunk.toString().trim();\n if (line) {\n // Subprocess stdout output handled silently unless needed\n }\n });\n\n this.childProcess.stderr?.on('data', (chunk) => {\n const line = chunk.toString().trim();\n if (line && !line.includes('ExperimentalWarning')) {\n // Forward warning if necessary\n }\n });\n\n this.childProcess.on('exit', (code, signal) => {\n if (!this.isStopped && code !== 0 && code !== null) {\n console.warn(`⚠️ [ProcessManager] Subprocess exited prematurely with code ${code}, signal ${signal}`);\n }\n });\n }\n\n public async waitForUrl(url: string, timeoutMs = 30000): Promise<boolean> {\n console.log(`⏳ [ProcessManager] Waiting for ${url} to respond...`);\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (this.childProcess && this.childProcess.exitCode !== null) {\n throw new Error(\n `[ProcessManager] Server process exited with code ${this.childProcess.exitCode} while waiting for ${url}`\n );\n }\n\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(1000) });\n if (response.status) {\n console.log(`✅ [ProcessManager] Target ${url} is ready (status: ${response.status})!`);\n return true;\n }\n } catch {\n // Retry on connection refused or timeout\n }\n\n await new Promise((r) => setTimeout(r, 500));\n }\n\n throw new Error(`[ProcessManager] Timeout after ${timeoutMs}ms waiting for ${url} to respond.`);\n }\n\n public async stop(): Promise<void> {\n if (this.isStopped || !this.childProcess || !this.childProcess.pid) {\n return;\n }\n\n this.isStopped = true;\n const pid = this.childProcess.pid;\n console.log(`🛑 [ProcessManager] Terminating process tree for PID ${pid}...`);\n\n await new Promise<void>((resolve) => {\n treeKill(pid, 'SIGTERM', (err) => {\n if (err) {\n try {\n treeKill(pid, 'SIGKILL');\n } catch {\n // ignore\n }\n }\n resolve();\n });\n });\n\n this.childProcess = null;\n }\n}","import path from 'path';\nimport fs from 'fs';\nimport { createJiti } from 'jiti';\nimport type { AgentLensPlugin, PluginHookContext, DriverLaunchResult } from '../api/plugin';\nimport type { TestContext } from '../api/dsl';\nimport type { ReportData } from '../types/report';\nimport type { Page, BrowserContext } from 'playwright';\n\nfunction findPackageRoot(): string {\n let cur = __dirname;\n while (cur !== path.dirname(cur)) {\n const pkgPath = path.join(cur, 'package.json');\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n if (pkg.name === '@_deep4wee/agent-lens' || pkg.name === 'agent-lens') {\n return cur;\n }\n } catch {\n // ignore\n }\n }\n cur = path.dirname(cur);\n }\n return path.resolve(__dirname, '..');\n}\n\nexport class PluginManager {\n private plugins: AgentLensPlugin[] = [];\n private jiti = createJiti(process.cwd());\n\n public register(plugin: AgentLensPlugin): void {\n if (this.plugins.some((p) => p.name === plugin.name)) {\n return;\n }\n this.plugins.push(plugin);\n console.log(`🔌 [Plugin] Registered: ${plugin.name}${plugin.version ? ` (v${plugin.version})` : ''}`);\n }\n\n public async load(pluginSpec: string | AgentLensPlugin): Promise<void> {\n if (typeof pluginSpec === 'object' && pluginSpec !== null) {\n this.register(pluginSpec);\n return;\n }\n\n const pluginNameOrPath = pluginSpec.trim();\n if (!pluginNameOrPath) return;\n\n // 1. Direct path (relative or absolute)\n if (\n pluginNameOrPath.startsWith('.') ||\n pluginNameOrPath.startsWith('/') ||\n pluginNameOrPath.includes('/') ||\n pluginNameOrPath.includes('\\\\')\n ) {\n const resolvedPath = path.resolve(process.cwd(), pluginNameOrPath);\n await this.loadFromFile(resolvedPath);\n return;\n }\n\n // 2. Local convention directories: .agent-lens/plugins/ or plugins/\n const localCandidates = [\n path.resolve(process.cwd(), '.agent-lens', 'plugins', `${pluginNameOrPath}.ts`),\n path.resolve(process.cwd(), '.agent-lens', 'plugins', `${pluginNameOrPath}.js`),\n path.resolve(process.cwd(), 'plugins', `${pluginNameOrPath}.ts`),\n path.resolve(process.cwd(), 'plugins', `${pluginNameOrPath}.js`),\n path.resolve(process.cwd(), '.agent-lens', 'plugins', pluginNameOrPath, 'index.ts'),\n path.resolve(process.cwd(), '.agent-lens', 'plugins', pluginNameOrPath, 'index.js'),\n path.resolve(process.cwd(), 'plugins', pluginNameOrPath, 'index.ts'),\n path.resolve(process.cwd(), 'plugins', pluginNameOrPath, 'index.js')\n ];\n\n for (const candidate of localCandidates) {\n if (fs.existsSync(candidate)) {\n await this.loadFromFile(candidate);\n return;\n }\n }\n\n // 3. Built-in plugins in agent-lens\n const pkgRoot = findPackageRoot();\n const builtInCandidates = [\n path.join(pkgRoot, 'src', 'plugins', pluginNameOrPath, 'index.ts'),\n path.join(pkgRoot, 'src', 'plugins', pluginNameOrPath, 'index.js'),\n path.join(pkgRoot, 'dist', 'plugins', pluginNameOrPath, 'index.js'),\n path.join(pkgRoot, 'plugins', pluginNameOrPath, 'index.ts'),\n path.join(pkgRoot, 'plugins', pluginNameOrPath, 'index.js'),\n path.resolve(__dirname, '../../plugins', pluginNameOrPath, 'index.ts'),\n path.resolve(__dirname, '../../plugins', pluginNameOrPath, 'index.js'),\n path.resolve(__dirname, '../plugins', pluginNameOrPath, 'index.ts'),\n path.resolve(__dirname, '../plugins', pluginNameOrPath, 'index.js')\n ];\n\n for (const builtIn of builtInCandidates) {\n if (fs.existsSync(builtIn)) {\n await this.loadFromFile(builtIn);\n return;\n }\n }\n\n // 4. Fallback to npm package resolution (e.g. @agent-lens/plugin-xxx)\n try {\n const mod = await this.jiti.import(pluginNameOrPath) as any;\n const plugin: AgentLensPlugin = mod.default || mod.plugin || mod;\n if (plugin && plugin.name) {\n this.register(plugin);\n } else {\n console.warn(`⚠️ [Plugin] Package \"${pluginNameOrPath}\" did not export a valid AgentLensPlugin.`);\n }\n } catch (err: any) {\n console.warn(`⚠️ [Plugin] Could not load plugin '${pluginNameOrPath}': ${err.message}`);\n }\n }\n\n private async loadFromFile(filePath: string): Promise<void> {\n if (!fs.existsSync(filePath)) {\n console.warn(`⚠️ [Plugin] Plugin file not found: ${filePath}`);\n return;\n }\n\n try {\n const mod = await this.jiti.import(filePath) as any;\n const plugin: AgentLensPlugin = mod.default || mod.plugin || mod;\n if (plugin && plugin.name) {\n this.register(plugin);\n } else {\n console.warn(`⚠️ [Plugin] File \"${filePath}\" does not export a valid AgentLensPlugin by default.`);\n }\n } catch (err: any) {\n console.error(`❌ [Plugin] Failed to import plugin from ${filePath}:`, err.message);\n }\n }\n\n public async loadAll(specs: (string | AgentLensPlugin)[]): Promise<void> {\n for (const spec of specs) {\n await this.load(spec);\n }\n }\n\n public async runSetup(hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.setup) await p.setup(hookContext);\n }\n }\n\n public async launchSession(\n options: { currentViewport: { width: number; height: number }; headed?: boolean },\n hookContext: PluginHookContext\n ): Promise<DriverLaunchResult | undefined> {\n for (const p of this.plugins) {\n if (p.launchSession) {\n const session = await p.launchSession(options, hookContext);\n if (session) {\n return session;\n }\n }\n }\n return undefined;\n }\n\n public async runOnContextCreated(context: BrowserContext, hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.onContextCreated) await p.onContextCreated(context, hookContext);\n }\n }\n\n public async runOnPageCreated(page: Page, context: BrowserContext, hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.onPageCreated) await p.onPageCreated(page, context, hookContext);\n }\n }\n\n public async extendContext(ctx: TestContext, page: Page, hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.extendContext) {\n const extensions = await p.extendContext(ctx, page, hookContext);\n if (extensions && typeof extensions === 'object') {\n Object.assign(ctx, extensions);\n }\n }\n }\n }\n\n public async runOnAfterRun(reportData: ReportData, hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.onAfterRun) await p.onAfterRun(reportData, hookContext);\n }\n }\n\n public async runTeardown(hookContext: PluginHookContext): Promise<void> {\n for (const p of this.plugins) {\n if (p.teardown) {\n try {\n await p.teardown(hookContext);\n } catch (e: any) {\n console.error(`⚠️ [Plugin] Error in ${p.name}.teardown:`, e.message);\n }\n }\n }\n }\n\n public get loadedPlugins(): AgentLensPlugin[] {\n return [...this.plugins];\n }\n}","import type { Page } from 'playwright';\n\nexport type NavigateFunction = (route: string) => Promise<void>;\n\n/**\n * Creates an intelligent route navigator supporting full URLs, hash routes,\n * base-relative paths, and SPA hash fallbacks.\n */\nexport function createNavigator(page: Page, baseUrl?: string, targetUrl?: string): NavigateFunction {\n return async (route: string): Promise<void> => {\n // 1. Direct absolute URLs\n if (route.startsWith('http://') || route.startsWith('https://')) {\n await page.goto(route, { waitUntil: 'domcontentloaded' });\n await page.waitForTimeout(300);\n return;\n }\n\n // 2. Hash-based routing (e.g. #/settings)\n if (route.startsWith('#')) {\n await page.evaluate((r) => { window.location.hash = r; }, route);\n await page.waitForTimeout(300);\n return;\n }\n\n // 3. Base URL resolution\n if (baseUrl && targetUrl) {\n try {\n const fullUrl = new URL(route, baseUrl).toString();\n await page.goto(fullUrl, { waitUntil: 'domcontentloaded' });\n await page.waitForTimeout(300);\n return;\n } catch {\n // Fall back to hash navigation on invalid URL format\n }\n }\n\n // 4. Default client-side SPA fallback\n const routePath = route.startsWith('/') ? route : `/${route}`;\n await page.evaluate((r) => {\n if (window.location.hash !== undefined) {\n window.location.hash = r;\n }\n }, routePath);\n await page.waitForTimeout(300);\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { SnapshotMetadata } from '../../../shared/api/dsl';\n\n/**\n * Synchronizes the generated test report and snapshots into artifacts/latest/\n * so agents always have immediate access to the freshest results without parsing timestamps.\n */\nexport function syncLatestArtifacts(\n artifactsRoot: string,\n scenarioArtifactsDir: string,\n reportPath: string,\n snapshots: SnapshotMetadata[]\n): string | undefined {\n try {\n const latestDir = path.join(artifactsRoot, 'latest');\n if (fs.existsSync(latestDir)) {\n fs.rmSync(latestDir, { recursive: true, force: true });\n }\n fs.mkdirSync(latestDir, { recursive: true });\n\n // 1. Copy markdown report\n if (fs.existsSync(reportPath)) {\n fs.copyFileSync(reportPath, path.join(latestDir, 'report.md'));\n }\n\n // 2. Copy manifest JSON if present\n const manifestSrc = path.join(scenarioArtifactsDir, 'manifest.json');\n if (fs.existsSync(manifestSrc)) {\n fs.copyFileSync(manifestSrc, path.join(latestDir, 'manifest.json'));\n }\n\n // 3. Copy individual image snapshots\n for (const snap of snapshots) {\n if (snap.filePath && fs.existsSync(snap.filePath)) {\n fs.copyFileSync(snap.filePath, path.join(latestDir, snap.fileName));\n }\n }\n\n return path.join(latestDir, 'report.md');\n } catch {\n return undefined;\n }\n}\n","import type { Page, BrowserContext } from 'playwright';\nimport type {\n TestContext,\n ViewportPreset,\n CaptureOptions,\n CaptureBurstOptions,\n MockRouteEntry\n} from '../../../shared/api/dsl';\nimport type { CaptureEngine } from '../../capture/capture';\nimport type { ConsoleTracker } from '../../console-tracker/consoleTracker';\nimport type { PreviewDriver } from '../../../shared/drivers/previewDriver';\nimport type { NavigateFunction } from './navigation';\n\nexport interface ContextBuilderOptions {\n page: Page;\n context: BrowserContext;\n targetMode: 'desktop' | 'preview' | string;\n initialViewport: { width: number; height: number };\n captureEngine: CaptureEngine;\n consoleTracker: ConsoleTracker;\n previewDriver: PreviewDriver | null;\n doNavigate: NavigateFunction;\n}\n\nexport function buildTestContext(options: ContextBuilderOptions): {\n ctx: TestContext;\n getCurrentViewport: () => { width: number; height: number };\n} {\n const {\n page,\n context,\n targetMode,\n initialViewport,\n captureEngine,\n consoleTracker,\n previewDriver,\n doNavigate\n } = options;\n\n let currentViewport = { ...initialViewport };\n\n const ctx: TestContext = {\n page,\n context,\n targetMode: targetMode as 'desktop' | 'preview',\n currentViewport,\n\n // --- Snapshots ---\n capture: async (name: string, opts?: CaptureOptions) => {\n console.log(`📸 [Snapshot] ${name} (${currentViewport.width}x${currentViewport.height})`);\n return await captureEngine.takeSnapshot(page, name, currentViewport, opts);\n },\n\n captureBurst: async (name: string, opts: CaptureBurstOptions) => {\n console.log(`🎬 [Burst] ${name} (duration: ${opts.durationMs}ms, interval: ${opts.intervalMs ?? 80}ms)`);\n return await captureEngine.takeBurst(page, name, currentViewport, opts);\n },\n\n // --- Navigation ---\n navigate: async (route: string) => {\n console.log(`🧭 [Navigate] ${route}`);\n await doNavigate(route);\n },\n\n // --- Viewport Management ---\n resize: async (width: number, height: number) => {\n console.log(`📐 [Resize] ${width}x${height}`);\n currentViewport = { width, height };\n ctx.currentViewport = currentViewport;\n await page.setViewportSize(currentViewport);\n await page.waitForTimeout(200);\n },\n\n setPreset: async (preset: ViewportPreset) => {\n console.log(`📐 [Preset] ${preset.name} (${preset.width}x${preset.height})`);\n await ctx.resize(preset.width, preset.height);\n },\n\n resizeToFit: async (selector?: string, padding = 0) => {\n let boundingBox;\n if (selector) {\n const el = await page.$(selector);\n if (el) {\n boundingBox = await el.boundingBox();\n }\n } else {\n boundingBox = await page.evaluate(() => ({\n width: document.documentElement.scrollWidth,\n height: document.documentElement.scrollHeight\n }));\n }\n\n if (boundingBox) {\n const newWidth = Math.ceil(boundingBox.width) + padding * 2;\n const newHeight = Math.ceil(boundingBox.height) + padding * 2;\n console.log(`📐 [ResizeToFit] ${selector || 'body'} -> ${newWidth}x${newHeight}`);\n await ctx.resize(newWidth, newHeight);\n } else {\n console.log(`⚠️ [ResizeToFit] Element ${selector} not found or has no bounding box.`);\n }\n },\n\n // --- Waiting & DOM Observation ---\n wait: async (ms: number) => {\n await page.waitForTimeout(ms);\n },\n\n waitForSelector: async (selector: string, timeoutMs = 5000) => {\n await page.waitForSelector(selector, { timeout: timeoutMs });\n },\n\n // --- Interaction ---\n click: async (selector: string) => {\n console.log(`🖱️ [Click] ${selector}`);\n await page.click(selector);\n },\n\n rightClick: async (selector: string) => {\n console.log(`🖱️ [RightClick] ${selector}`);\n await page.click(selector, { button: 'right' });\n },\n\n type: async (selector: string, text: string) => {\n console.log(`⌨️ [Type] ${selector} -> \"${text}\"`);\n await page.fill(selector, text);\n },\n\n selectOption: async (selector: string, value: string) => {\n console.log(`✅ [Select] ${selector} -> \"${value}\"`);\n await page.selectOption(selector, value);\n },\n\n hover: async (selector: string) => {\n console.log(`👆 [Hover] ${selector}`);\n await page.hover(selector);\n },\n\n scroll: async (selector: string, deltaY: number) => {\n console.log(`📜 [Scroll] ${selector} by ${deltaY}px`);\n await page.evaluate(\n ({ sel, dY }) => {\n const el = document.querySelector(sel);\n if (el) {\n el.scrollTop += dY;\n } else {\n window.scrollBy(0, dY);\n }\n },\n { sel: selector, dY: deltaY }\n );\n await page.waitForTimeout(100);\n },\n\n // --- Logging ---\n log: (msg: string) => {\n console.log(`ℹ️ [Scenario] ${msg}`);\n },\n\n // --- Mock IPC (delegated or stubbed if plugin not loaded) ---\n setMockIpc: async (action: string, data: any, mockOptions?: any) => {\n console.log(`⚠️ [Mock IPC] setMockIpc called for \"${action}\". Ensure --plugin=mock-ipc is enabled.`);\n },\n\n // --- HTTP Route Mocking ---\n setMockRoute: async (\n url: string,\n body: any,\n routeOptions?: { method?: string; status?: number; delayMs?: number; headers?: Record<string, string> }\n ) => {\n if (!previewDriver) {\n console.log(`⚠️ [Mock Route] setMockRoute ignored (no preview driver running)`);\n return;\n }\n console.log(`🌐 [Mock Route] Intercepting ${routeOptions?.method || 'ALL'} ${url} -> ${routeOptions?.status ?? 200}`);\n await previewDriver.addRouteMock({\n url,\n body,\n method: routeOptions?.method,\n status: routeOptions?.status,\n delayMs: routeOptions?.delayMs,\n headers: routeOptions?.headers\n });\n },\n\n // --- Console Errors ---\n getConsoleErrors: () => consoleTracker.getErrors(),\n getConsoleWarnings: () => consoleTracker.getWarnings(),\n hasConsoleErrors: () => consoleTracker.hasErrors,\n\n // --- DOM Assertions ---\n readText: async (selector: string): Promise<string | null> => {\n const text = await page.textContent(selector);\n return text ? text.trim() : null;\n },\n\n getPageText: async (): Promise<string> => {\n return await page.evaluate(() => document.body.innerText || '');\n },\n\n isVisible: async (selector: string) => {\n try {\n const element = await page.$(selector);\n if (!element) return false;\n return await element.isVisible();\n } catch {\n return false;\n }\n },\n\n getElementCount: async (selector: string) => {\n const elements = await page.$$(selector);\n return elements.length;\n }\n };\n\n return {\n ctx,\n getCurrentViewport: () => currentViewport\n };\n}\n","import path from 'path';\nimport fs from 'fs';\nimport { runVisualScenario } from '../runner/runner';\nimport { defineVisualTest, VIEWPORT_PRESETS, type ViewportPreset } from '../../shared/api/dsl';\nimport { resolveWwwrootDir } from '../../shared/lib/config';\n\nexport interface SnapCliOptions {\n url?: string;\n start?: string;\n startCwd?: string;\n selector?: string;\n viewports?: string[];\n waitMs?: number;\n name?: string;\n clean?: string[];\n cleanArtifacts?: boolean;\n headed?: boolean;\n detach?: boolean;\n outDir?: string;\n mode?: 'desktop' | 'preview';\n exe?: string;\n port?: number;\n plugins?: (string | any)[];\n fullPage?: boolean;\n}\n\nconst PRESET_MAP: Record<string, ViewportPreset> = {\n \n default: VIEWPORT_PRESETS.DEFAULT,\n desktop: VIEWPORT_PRESETS.DEFAULT,\n min: VIEWPORT_PRESETS.MIN_SUPPORTED,\n 'min-supported': VIEWPORT_PRESETS.MIN_SUPPORTED,\n wide: VIEWPORT_PRESETS.WIDE,\n mobile: { name: 'mobile', width: 375, height: 667 },\n tablet: { name: 'tablet', width: 768, height: 1024 },\n 'full-hd': VIEWPORT_PRESETS.FULL_HD\n};\n\nfunction parseViewportPresets(raw?: string[]): ViewportPreset[] {\n if (!raw || raw.length === 0) {\n return [VIEWPORT_PRESETS.DEFAULT, PRESET_MAP.mobile];\n }\n\n const presets: ViewportPreset[] = [];\n for (const item of raw) {\n const lower = item.toLowerCase().trim();\n if (PRESET_MAP[lower]) {\n presets.push(PRESET_MAP[lower]);\n } else if (lower.includes('x')) {\n const [w, h] = lower.split('x').map((n) => parseInt(n, 10));\n if (!isNaN(w) && !isNaN(h)) {\n presets.push({ name: `${w}x${h}`, width: w, height: h });\n }\n }\n }\n\n return presets.length > 0 ? presets : [VIEWPORT_PRESETS.DEFAULT, PRESET_MAP.mobile];\n}\n\nconst CANDIDATE_PORTS = [5173, 3000, 4321, 4200, 8080, 8000, 3001];\n\nasync function isPortResponding(url: string): Promise<boolean> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(800) });\n return Boolean(res.status);\n } catch {\n return false;\n }\n}\n\nasync function detectActiveDevServer(): Promise<string | null> {\n for (const port of CANDIDATE_PORTS) {\n const url = `http://localhost:${port}`;\n if (await isPortResponding(url)) {\n return url;\n }\n }\n return null;\n}\n\nexport async function runQuickSnap(options: SnapCliOptions): Promise<boolean> {\n let targetUrl = options.url;\n let useStaticPreview = false;\n let wwwrootDir: string | undefined;\n\n // Smart URL/Server Resolution when neither --url nor --start is explicitly provided\n if (!targetUrl && !options.start) {\n const activeUrl = await detectActiveDevServer();\n if (activeUrl) {\n targetUrl = activeUrl;\n console.log(`🌐 [Quick Snap] Detected active dev server on ${targetUrl}`);\n } else {\n // Check for compiled static files (dist, build, wwwroot)\n const candidateDir = resolveWwwrootDir();\n if (fs.existsSync(candidateDir) && fs.existsSync(path.join(candidateDir, 'index.html'))) {\n useStaticPreview = true;\n wwwrootDir = candidateDir;\n console.log(`📦 [Quick Snap] No active server found. Detected built static directory at \"${candidateDir}\". Launching static preview...`);\n } else {\n console.error(`\\n❌ [Quick Snap] No active server found on common ports (${CANDIDATE_PORTS.join(', ')}), and no static build found.`);\n console.log(`💡 Suggested actions:`);\n console.log(` 1. Pass a start command: npx agent-lens snap --start=\"npm run dev\"`);\n console.log(` 2. Specify your URL: npx agent-lens snap --url=http://localhost:8080`);\n console.log(` 3. Build your static app: npm run build\\n`);\n return false;\n }\n }\n } else if (!targetUrl && options.start) {\n targetUrl = 'http://localhost:5173';\n }\n \n\n const waitMs = options.waitMs ?? 1000;\n const snapshotPrefix = options.name || 'quick_snap';\n const targetViewports = parseViewportPresets(options.viewports);\n\n console.log(`\\n📸 [Quick Snap] Preparing instant verification for: ${targetUrl || wwwrootDir}`);\n console.log(`📐 [Quick Snap] Testing ${targetViewports.length} viewports: ${targetViewports.map((v) => `${v.name} (${v.width}x${v.height})`).join(', ')}`);\n if (options.selector) {\n console.log(`🎯 [Quick Snap] Focused element selector: \"${options.selector}\"`);\n }\n\n const snapScenario = defineVisualTest({\n id: 'quick-snap',\n title: `Quick Verification: ${targetUrl || 'Static Preview'}`,\n description: `One-shot automated snapshot and console health check`,\n route: targetUrl || '/',\n viewports: targetViewports,\n run: async (ctx) => {\n ctx.log(`Waiting ${waitMs}ms for page stabilization...`);\n await ctx.wait(waitMs);\n\n for (let i = 0; i < targetViewports.length; i++) {\n const vp = targetViewports[i];\n const stepNum = String(i + 1).padStart(2, '0');\n ctx.log(`Switching viewport to: ${vp.name} (${vp.width}x${vp.height})`);\n await ctx.setPreset(vp);\n await ctx.wait(200);\n await ctx.capture(`${stepNum}_${snapshotPrefix}_${vp.name}`, { fullPage: options.fullPage });\n }\n\n if (options.selector) {\n ctx.log(`Focusing on selector: \"${options.selector}\"`);\n try {\n await ctx.setPreset(VIEWPORT_PRESETS.DEFAULT);\n await ctx.resizeToFit(options.selector, 15);\n await ctx.capture(`99_${snapshotPrefix}_element_focus`, { selector: options.selector });\n } catch (err: any) {\n ctx.log(`⚠️ Could not isolate selector \"${options.selector}\": ${err.message}`);\n }\n }\n\n const errors = ctx.getConsoleErrors();\n if (errors.length > 0) {\n ctx.log(`🚨 Caught ${errors.length} console errors during snap check!`);\n } else {\n ctx.log(`✅ Clean run: No console errors detected.`);\n }\n }\n });\n\n const result = await runVisualScenario({\n scenario: snapScenario,\n targetMode: options.mode || 'preview',\n url: useStaticPreview ? undefined : targetUrl,\n wwwrootDir: useStaticPreview ? wwwrootDir : undefined,\n startCommand: options.start,\n startCwd: options.startCwd,\n executablePath: options.exe,\n cleanPaths: options.clean,\n cleanArtifacts: options.cleanArtifacts,\n port: options.port,\n headed: options.headed,\n detach: options.detach,\n artifactsRoot: options.outDir ? path.resolve(process.cwd(), options.outDir) : undefined,\n plugins: options.plugins\n });\n \n\n console.log(`\\n========================================`);\n console.log(`🏁 Quick Snap Finished!`);\n console.log(`📸 Snapshots: ${result.totalSnapshots}`);\n console.log(`🔴 Console Errors: ${result.consoleErrors}`);\n console.log(`🟡 Console Warnings: ${result.consoleWarnings}`);\n console.log(`📄 Report: ${result.reportPath}`);\n console.log(`========================================\\n`);\n\n return result.success && result.consoleErrors === 0;\n}\n"],"mappings":";AA0DO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;;;AC7CO,IAAM,mBAAmB;AAAA;AAAA,EAE9B,eAAe,EAAE,MAAM,iBAAiB,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAEjE,SAAS,EAAE,MAAM,WAAW,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAErD,MAAM,EAAE,MAAM,QAAQ,OAAO,MAAM,QAAQ,IAAI;AAAA;AAAA,EAE/C,SAAS,EAAE,MAAM,WAAW,OAAO,MAAM,QAAQ,KAAK;AACxD;AAuHO,SAAS,iBAAiB,UAA0C;AACzE,SAAO;AACT;;;ACjJA,OAAOA,WAAU;AACjB,OAAOC,SAAQ;;;ACDf,OAAO,QAAQ;AACf,OAAO,UAAU;AAIV,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EACA,mBAA2B;AAAA,EAC3B,oBAAwC,CAAC;AAAA,EAEjD,YAAY,WAAmB;AAC7B,SAAK,YAAY;AACjB,QAAI,CAAC,GAAG,WAAW,KAAK,SAAS,GAAG;AAClC,SAAG,UAAU,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAClD;AAAA,EACF;AAAA,EAEO,eAAmC;AACxC,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,eAAuB;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,MAAa,aACX,MACA,MACA,UACA,SAC2B;AAC3B,SAAK,oBAAoB;AACzB,UAAM,cAAc,OAAO,KAAK,gBAAgB,EAAE,SAAS,GAAG,GAAG;AACjE,UAAM,gBAAgB,KAAK,QAAQ,oBAAoB,GAAG;AAC1D,UAAM,WAAW,GAAG,WAAW,IAAI,aAAa,IAAI,SAAS,KAAK,IAAI,SAAS,MAAM;AACrF,UAAM,WAAW,KAAK,KAAK,KAAK,WAAW,QAAQ;AAEnD,QAAI,SAAS,UAAU;AACrB,YAAM,UAAU,MAAM,KAAK,gBAAgB,QAAQ,UAAU,EAAE,SAAS,IAAK,CAAC;AAC9E,YAAM,QAAQ,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,IAC7C,OAAO;AACL,YAAM,KAAK,WAAW,EAAE,MAAM,UAAU,UAAU,SAAS,YAAY,MAAM,CAAC;AAAA,IAChF;AAEA,UAAM,WAA6B;AAAA,MACjC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,KAAK,QAAQ;AAAA,MAC1B;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,UAAU,SAAS;AAAA,IACrB;AAEA,SAAK,kBAAkB,KAAK,QAAQ;AACpC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,MAAa,UACX,MACA,MACA,UACA,SAC6B;AAC7B,SAAK,oBAAoB;AACzB,UAAM,YAAY,KAAK;AACvB,UAAM,cAAc,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG;AACrD,UAAM,gBAAgB,KAAK,QAAQ,oBAAoB,GAAG;AAE1D,UAAM,WAAW,KAAK,IAAI,QAAQ,YAAY,EAAE;AAChD,UAAM,WAAW,KAAK,IAAI,QAAQ,cAAc,IAAI,EAAE;AACtD,UAAM,cAAc,KAAK,KAAK,WAAW,QAAQ;AAEjD,UAAM,iBAAqC,CAAC;AAC5C,UAAM,aAAa;AAEnB,aAAS,QAAQ,GAAG,SAAS,aAAa,SAAS;AACjD,YAAM,YAAY,QAAQ;AAC1B,YAAM,cAAc,OAAO,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,YAAM,WAAW,GAAG,WAAW,UAAU,aAAa,KAAK,WAAW,IAAI,SAAS;AACnF,YAAM,WAAW,KAAK,KAAK,KAAK,WAAW,QAAQ;AAEnD,UAAI,QAAQ,UAAU;AACpB,cAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,QAAQ;AAC7C,YAAI,SAAS;AACX,gBAAM,QAAQ,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,QAC7C,OAAO;AACL,gBAAM,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,QAC1C;AAAA,MACF,OAAO;AACL,cAAM,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,MAC1C;AAEA,YAAM,OAAyB;AAAA,QAC7B,OAAO;AAAA,QACP,MAAM,GAAG,IAAI,WAAW,QAAQ,CAAC,MAAM,SAAS;AAAA,QAChD;AAAA,QACA;AAAA,QACA,aAAa,KAAK,QAAQ;AAAA,QAC1B;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,cAAc;AAAA,QACd;AAAA,QACA,YAAY,QAAQ;AAAA,QACpB,UAAU,QAAQ;AAAA,MACpB;AAEA,qBAAe,KAAK,IAAI;AACxB,WAAK,kBAAkB,KAAK,IAAI;AAEhC,UAAI,QAAQ,aAAa;AACvB,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAAA,MAClD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;AC1HA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAQV,IAAM,iBAAN,MAAqB;AAAA,EAC1B,OAAc,eAAe,MAA0B;AACrD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAEJ,UAAM,eAAeA,MAAK,KAAK,WAAW,eAAe;AACzD,UAAM,aAAaA,MAAK,KAAK,WAAW,WAAW;AAEnD,UAAM,WAAW;AAAA,MACf,YAAY,SAAS;AAAA,MACrB,OAAO,SAAS;AAAA,MAChB,aAAa,SAAS;AAAA,MACtB;AAAA,MACA;AAAA,MACA,gBAAgB,UAAU;AAAA,MAC1B,eAAe,cAAc;AAAA,MAC7B,iBAAiB,gBAAgB;AAAA,MACjC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AACA,IAAAD,IAAG,cAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AAEzE,UAAM,OAAiB,CAAC;AAExB,UAAM,mBAAmB,UAAU,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY;AAChE,UAAM,cAAc,oBAAI,IAAgC;AAExD,cAAU,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM;AACrE,YAAM,OAAO,YAAY,IAAI,EAAE,UAAW,KAAK,CAAC;AAChD,WAAK,KAAK,CAAC;AACX,kBAAY,IAAI,EAAE,YAAa,IAAI;AAAA,IACrC,CAAC;AAEG,eAAW,QAAQ,kBAAkB;AACvC,YAAM,eAAe,KAAK,eAAe,KAAK,KAAK,QAAQ;AAC3D,WAAK;AAAA,QACH,OAAO,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,UAAU,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,QAAQ,KAAK,YAAY;AAAA,MACvJ;AAAA,IACF;AAEA,QAAI,gBAAgB;AACpB,QAAI,YAAY,OAAO,GAAG;AACxB,uBAAiB;AAAA;AAAA;AAAA;AACjB,iBAAW,CAAC,OAAO,MAAM,KAAK,YAAY,QAAQ,GAAG;AACnD,yBAAiB,qBAAqB,KAAK,OAAO,OAAO,MAAM;AAAA;AAAA;AAC/D,yBAAiB;AAAA;AAAA;AACjB,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,eAAe,MAAM,eAAe,KAAK,MAAM,QAAQ;AAC7D,2BAAiB,WAAW,MAAM,UAAU,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,SAAS,MAAM,SAAS,MAAM,QAAQ,KAAK,YAAY,WAAW,YAAY;AAAA;AAAA,QAClK;AACA,yBAAiB;AAAA;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,eAAe,KAAK,gBAAgB,eAAe,eAAe;AACxE,QAAI,kBAAkB;AAEtB,QAAI,cAAc,SAAS,GAAG;AAC5B,yBAAmB;AAAA,+BAA2B,cAAc,MAAM;AAAA;AAAA;AAClE,yBAAmB;AAAA,YAA2B,cAAc,MAAM;AAAA;AAAA;AAClE,yBAAmB;AAAA;AAAA;AACnB,oBAAc,QAAQ,CAAC,KAAK,MAAM;AAChC,cAAM,OAAO,IAAI,UAAU,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK;AACzD,cAAM,OAAO,IAAI,KAAK,QAAQ,OAAO,KAAK,EAAE,MAAM,GAAG,GAAG;AACxD,cAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK;AACxC,2BAAmB,KAAK,IAAI,CAAC,QAAQ,IAAI,QAAQ,IAAI,QAAQ,GAAG;AAAA;AAAA,MAClE,CAAC;AAED,YAAM,YAAY,cAAc,OAAO,OAAK,EAAE,KAAK;AACnD,UAAI,UAAU,SAAS,GAAG;AACxB,2BAAmB;AAAA;AAAA,mCAA0C,UAAU,MAAM;AAAA;AAAA;AAC7E,kBAAU,QAAQ,CAAC,KAAK,MAAM;AAC5B,6BAAmB,WAAW,IAAI,CAAC,SAAS,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA;AAAA,EAAe,IAAI,KAAK;AAAA;AAAA;AAAA;AAAA,QAC5F,CAAC;AACD,2BAAmB;AAAA;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,gBAAgB,SAAS,GAAG;AAC9B,yBAAmB;AAAA,iCAA6B,gBAAgB,MAAM;AAAA;AAAA;AACtE,yBAAmB;AAAA;AAAA;AACnB,sBAAgB,MAAM,GAAG,EAAE,EAAE,QAAQ,CAAC,MAAM,MAAM;AAChD,cAAM,OAAO,KAAK,KAAK,QAAQ,OAAO,KAAK,EAAE,MAAM,GAAG,GAAG;AACzD,2BAAmB,KAAK,IAAI,CAAC,MAAM,IAAI;AAAA;AAAA,MACzC,CAAC;AACD,UAAI,gBAAgB,SAAS,IAAI;AAC/B,2BAAmB;AAAA,UAAa,gBAAgB,SAAS,EAAE;AAAA;AAAA,MAC7D;AAAA,IACF;AAEA,QAAI,iBAAiB;AACrB,QAAI,KAAK,kBAAkB,KAAK,eAAe,SAAS,GAAG;AACzD,iBAAW,OAAO,KAAK,gBAAgB;AACrC,0BAAkB;AAAA,KAAQ,IAAI,KAAK;AAAA;AAAA,EAAO,IAAI,OAAO;AAAA;AAAA;AAAA,MACvD;AAAA,IACF;AAEA,UAAM,gBAAgB,mCAA4B,SAAS,KAAK;AAAA;AAAA,uBAE7C,SAAS,EAAE;AAAA,uBACX,WAAW,YAAY,CAAC,OAAO,eAAe,YAAY,kCAAkC,kBAAkB;AAAA,6BACxG,aAAa,KAAM,QAAQ,CAAC,CAAC;AAAA,kCACxB,UAAU,MAAM;AAAA,uBAC3B,YAAY;AAAA,4BACP,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnC,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,EAEf,aAAa;AAAA,EACb,eAAe;AAAA,EACf,cAAc;AAAA;AAAA;AAAA;AAAA,4BAIY,cAAc,WAAW,IAAI,2BAAsB,UAAK,cAAc,MAAM,4BAAuB;AAAA,8EACjD,iBAAiB,CAAC,GAAG,YAAY,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUvH,IAAAA,IAAG,cAAc,YAAY,eAAe,OAAO;AACnD,WAAO;AAAA,EACT;AAAA,EAEA,OAAe,gBAAgB,QAAwB,UAAkC;AACvF,QAAI,OAAO,WAAW,KAAK,SAAS,WAAW,GAAG;AAChD,aAAO;AAAA,IACT;AACA,QAAI,OAAO,WAAW,KAAK,SAAS,SAAS,GAAG;AAC9C,aAAO,0BAAgB,SAAS,MAAM;AAAA,IACxC;AACA,WAAO,kBAAa,OAAO,MAAM,YAAY,SAAS,MAAM;AAAA,EAC9D;AACF;;;AC7JO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UAA0B,CAAC;AAAA,EAC3B,WAAW;AAAA,EAEZ,OAAO,MAAkB;AAC9B,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAEhB,SAAK,GAAG,WAAW,CAAC,QAAwB;AAC1C,YAAM,OAAO,IAAI,KAAK;AACtB,YAAM,QAAQ,KAAK,eAAe,IAAI;AAEtC,YAAM,QAAsB;AAAA,QAC1B;AAAA,QACA,MAAM,IAAI,KAAK;AAAA,QACf,KAAK,KAAK,IAAI;AAAA,QACd,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAEA,WAAK,QAAQ,KAAK,KAAK;AAEvB,UAAI,UAAU,SAAS;AACrB,gBAAQ,IAAI,6BAAsB,IAAI,KAAK,CAAC,EAAE;AAAA,MAChD,WAAW,UAAU,WAAW;AAC9B,YAAI,CAAC,KAAK,iBAAiB,IAAI,KAAK,CAAC,GAAG;AACtC,kBAAQ,IAAI,4BAAqB,IAAI,KAAK,CAAC,EAAE;AAAA,QAC/C;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,GAAG,aAAa,CAAC,UAAiB;AACrC,YAAM,QAAsB;AAAA,QAC1B,OAAO;AAAA,QACP,MAAM,MAAM;AAAA,QACZ,KAAK,KAAK,IAAI;AAAA,QACd,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO,MAAM;AAAA,MACf;AAEA,WAAK,QAAQ,KAAK,KAAK;AACvB,cAAQ,IAAI,0BAAmB,MAAM,OAAO,EAAE;AAAA,IAChD,CAAC;AAAA,EACH;AAAA,EAEO,YAA4B;AACjC,WAAO,KAAK,QAAQ,OAAO,OAAK,EAAE,UAAU,OAAO;AAAA,EACrD;AAAA,EAEO,cAA8B;AACnC,WAAO,KAAK,QACT,OAAO,OAAK,EAAE,UAAU,SAAS,EACjC,OAAO,OAAK,CAAC,KAAK,iBAAiB,EAAE,IAAI,CAAC;AAAA,EAC/C;AAAA,EAEO,SAAyB;AAC9B,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,IAAW,aAAqB;AAC9B,WAAO,KAAK,UAAU,EAAE;AAAA,EAC1B;AAAA,EAEA,IAAW,eAAuB;AAChC,WAAO,KAAK,YAAY,EAAE;AAAA,EAC5B;AAAA,EAEA,IAAW,YAAqB;AAC9B,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA,EAEO,QAAc;AACnB,SAAK,UAAU,CAAC;AAAA,EAClB;AAAA,EAGQ,eAAe,MAAqC;AAC1D,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAS,eAAO;AAAA,MACrB,KAAK;AAAW,eAAO;AAAA,MACvB,KAAK;AAAQ,eAAO;AAAA,MACpB,KAAK;AAAS,eAAO;AAAA,MACrB;AAAS,eAAO;AAAA,IAClB;AAAA,EACF;AAAA,EAGQ,iBAAiB,MAAuB;AAC9C,UAAM,kBAAkB;AAAA,MACtB;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,IACF;AACA,WAAO,gBAAgB,KAAK,aAAW,KAAK,SAAS,OAAO,CAAC;AAAA,EAC/D;AACF;;;AC1GA,OAAO,UAAU;AACjB,OAAOE,SAAQ;AACf,OAAOC,WAAU;;;ACFjB,SAAS,YAAY,oBAA4D;AAK1E,IAAM,WAAyC,IAAI,MAAM,cAAc;AAAA,EAC5E,IAAI,QAAQ,MAAM,UAAU;AAC1B,QAAI,SAAS,UAAU;AACrB,aAAO,UAAU,SAAiD;AAChE,YAAI;AACF,iBAAO,MAAM,OAAO,OAAO,GAAG,IAAI;AAAA,QACpC,SAAS,KAAU;AACjB,cACE,IAAI,SAAS,SAAS,0BAA0B,KAChD,IAAI,SAAS,SAAS,oBAAoB,KAC1C,IAAI,SAAS,SAAS,iCAAiC,GACvD;AACA,oBAAQ,MAAM,qEAAgE;AAC9E,oBAAQ,MAAM,2EAAoE;AAAA,UACpF;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,MAAM,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAC9C,WAAO,OAAO,QAAQ,aAAa,IAAI,KAAK,MAAM,IAAI;AAAA,EACxD;AACF,CAAC;;;AC3BD,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAsBR,SAAS,WAAW,MAAc,QAAQ,IAAI,GAAwB;AAC3E,QAAM,iBAAiBD,MAAK,KAAK,KAAK,iBAAiB;AACvD,MAAIC,IAAG,WAAW,cAAc,GAAG;AACjC,QAAI;AACF,YAAM,MAAMA,IAAG,aAAa,gBAAgB,OAAO;AACnD,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,SAAS,GAAQ;AACf,cAAQ,KAAK,0DAAgD,EAAE,OAAO,EAAE;AAAA,IAC1E;AAAA,EACF;AAEA,QAAM,kBAAkBD,MAAK,KAAK,KAAK,cAAc;AACrD,MAAIC,IAAG,WAAW,eAAe,GAAG;AAClC,QAAI;AACF,YAAM,MAAMA,IAAG,aAAa,iBAAiB,OAAO;AACpD,YAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,UAAI,IAAI,aAAa,OAAO,IAAI,cAAc,UAAU;AACtD,eAAO,IAAI;AAAA,MACb;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,CAAC;AACV;AAEO,SAAS,eAAe,aAA0C;AACvE,MAAI,aAAa;AACf,UAAM,WAAWD,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW;AACxD,QAAIC,IAAG,WAAW,QAAQ,GAAG;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAcD,MAAK,KAAK,QAAQ,IAAI,GAAG,cAAc;AAC3D,MAAI,mBAAmB;AACvB,MAAIC,IAAG,WAAW,WAAW,GAAG;AAC9B,QAAI;AACF,YAAM,UAAU,KAAK,MAAMA,IAAG,aAAa,aAAa,OAAO,CAAC;AAChE,yBAAmB,QAAQ,QAAQ,SAAS,OAAO,QAAQ,SAAS,KAAK;AAAA,IAC3E,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,kBAAkB;AACpB,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,CAAC,YAAY,YAAY,UAAU,OAAO,MAAM,YAAY,cAAc;AAC7F,aAAW,aAAa,YAAY;AAClC,UAAM,eAAeD,MAAK,KAAK,QAAQ,IAAI,GAAG,WAAW,cAAc;AACvE,QAAIC,IAAG,WAAW,YAAY,GAAG;AAC/B,aAAO,KAAK,SAAS;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,WAA4B;AAC5D,MAAI,WAAW;AACb,WAAOD,MAAK,QAAQ,QAAQ,IAAI,GAAG,SAAS;AAAA,EAC9C;AAEA,QAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,aAAW,KAAK,YAAY;AAC1B,UAAM,gBAAgBA,MAAK,QAAQ,QAAQ,IAAI,GAAG,CAAC;AACnD,QAAIC,IAAG,WAAW,aAAa,KAAKA,IAAG,WAAWD,MAAK,KAAK,eAAe,YAAY,CAAC,GAAG;AACzF,aAAO;AAAA,IACT;AAAA,EACF;AAEA,aAAW,KAAK,YAAY;AAC1B,UAAM,gBAAgBA,MAAK,QAAQ,QAAQ,IAAI,GAAG,CAAC;AACnD,QAAIC,IAAG,WAAW,aAAa,GAAG;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAOD,MAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM;AAC3C;;;AF1GA,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AACV;AAQO,IAAM,gBAAN,MAAoB;AAAA,EACjB,SAA6B;AAAA,EAC7B,UAA0B;AAAA,EAC1B,UAAiC;AAAA,EACjC,OAAoB;AAAA,EACpB,aAAqB;AAAA,EACrB;AAAA,EACA,WAAmB;AAAA,EACnB,oBAAsC,CAAC;AAAA,EAE/C,YAAY,SAAgC;AAC1C,SAAK,UAAU,WAAW,CAAC;AAC3B,QAAI,CAAC,KAAK,QAAQ,KAAK;AACrB,WAAK,QAAQ,aAAa,kBAAkB,KAAK,QAAQ,UAAU;AAAA,IACrE;AAAA,EACF;AAAA,EAEA,IAAW,UAAkB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAa,MAAM,kBAAkB,EAAE,OAAO,MAAM,QAAQ,IAAI,GAAuE;AACrI,QAAI,YAAY,KAAK,QAAQ;AAE7B,QAAI,CAAC,WAAW;AACd,UAAI,CAACE,IAAG,WAAW,KAAK,QAAQ,UAAW,GAAG;AAC5C,cAAM,IAAI,MAAM,2BAA2B,KAAK,QAAQ,UAAU,iEAAiE;AAAA,MACrI;AAEA,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,aAAK,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC5C,cAAI,SAAS,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AACvC,cAAI,WAAW,IAAK,UAAS;AAE7B,cAAI,WAAWC,MAAK,UAAUA,MAAK,KAAK,KAAK,QAAQ,YAAa,MAAM,CAAC;AACzE,cAAI,CAAC,SAAS,WAAW,KAAK,QAAQ,UAAW,GAAG;AAClD,gBAAI,UAAU,GAAG;AACjB,mBAAO,IAAI,IAAI,WAAW;AAAA,UAC5B;AAEA,cAAI,CAACD,IAAG,WAAW,QAAQ,KAAKA,IAAG,SAAS,QAAQ,EAAE,YAAY,GAAG;AAEnE,uBAAWC,MAAK,KAAK,KAAK,QAAQ,YAAa,YAAY;AAAA,UAC7D;AAEA,gBAAM,MAAMA,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAC/C,gBAAM,cAAc,WAAW,GAAG,KAAK;AAEvC,cAAI;AACF,kBAAM,UAAUD,IAAG,aAAa,QAAQ;AACxC,gBAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,gBAAI,IAAI,OAAO;AAAA,UACjB,SAAS,GAAQ;AACf,gBAAI,UAAU,GAAG;AACjB,gBAAI,IAAI,iBAAiB,EAAE,OAAO,EAAE;AAAA,UACtC;AAAA,QACF,CAAC;AAED,aAAK,OAAO,OAAO,GAAG,aAAa,MAAM;AACvC,gBAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,cAAI,OAAO,SAAS,YAAY,MAAM,MAAM;AAC1C,iBAAK,aAAa,KAAK;AACvB,iBAAK,WAAW,oBAAoB,KAAK,UAAU;AACnD,oBAAQ;AAAA,UACV,OAAO;AACL,mBAAO,IAAI,MAAM,kDAAkD,CAAC;AAAA,UACtE;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,kBAAY,GAAG,KAAK,QAAQ;AAAA,IAC9B,OAAO;AACL,WAAK,WAAW;AAChB,cAAQ,IAAI,8DAAuD,SAAS,EAAE;AAAA,IAChF;AAEA,SAAK,UAAU,MAAM,SAAS,OAAO;AAAA,MACnC,UAAU,CAAC,KAAK,QAAQ;AAAA,MACxB,MAAM,CAAC,gBAAgB,0BAA0B;AAAA,IACnD,CAAC;AAED,SAAK,UAAU,MAAM,KAAK,QAAQ,WAAW;AAAA,MAC3C,UAAU;AAAA,MACV,mBAAmB;AAAA,IACrB,CAAC;AAED,SAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ;AAEvC,QAAI,KAAK,kBAAkB,SAAS,GAAG;AACrC,iBAAW,SAAS,KAAK,mBAAmB;AAC1C,cAAM,KAAK,aAAa,KAAK;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,KAAK,KAAK,KAAK,WAAW,EAAE,WAAW,mBAAmB,CAAC;AAEjE,WAAO,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,SAAS,KAAK,QAAQ;AAAA,EACzE;AAAA,EAEA,MAAa,aAAa,OAAsC;AAC9D,QAAI,CAAC,KAAK,MAAM;AACd,WAAK,kBAAkB,KAAK,KAAK;AACjC;AAAA,IACF;AAEA,UAAM,KAAK,KAAK,MAAM,MAAM,KAAK,OAAO,UAAU;AAChD,YAAM,MAAM,MAAM,QAAQ;AAC1B,UAAI,MAAM,UAAU,IAAI,OAAO,EAAE,YAAY,MAAM,MAAM,OAAO,YAAY,GAAG;AAC7E,eAAO,MAAM,SAAS;AAAA,MACxB;AAEA,UAAI,MAAM,SAAS;AACjB,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC;AAAA,MACvD;AAEA,YAAM,SAAS,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS;AAChE,YAAM,MAAM,QAAQ;AAAA,QAClB,QAAQ,MAAM,UAAU;AAAA,QACxB,aAAa,SAAS,qBAAqB;AAAA,QAC3C,MAAM,SAAS,KAAK,UAAU,MAAM,IAAI,IAAI,OAAO,MAAM,QAAQ,EAAE;AAAA,QACnE,SAAS,MAAM;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,gBAAgB,QAAyC;AACpE,eAAW,KAAK,QAAQ;AACtB,YAAM,KAAK,aAAa,CAAC;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAa,OAAsB;AACjC,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACzC,WAAK,UAAU;AAAA,IACjB;AACA,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACzC,WAAK,UAAU;AAAA,IACjB;AACA,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,QAAQ,MAAM,MAAM,QAAQ,CAAC;AAAA,MACpC,CAAC;AACD,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AACF;;;AGjLA,SAAS,aAAgC;AACzC,OAAO,cAAc;AAQd,IAAM,iBAAN,MAAqB;AAAA,EAClB,eAAoC;AAAA,EACpC,YAAY;AAAA,EAEpB,MAAa,MAAM,SAAiB,SAA8C;AAChF,UAAM,MAAM,SAAS,OAAO,QAAQ,IAAI;AACxC,YAAQ,IAAI,iDAA0C,OAAO,QAAQ,GAAG,EAAE;AAE1E,SAAK,eAAe,MAAM,SAAS;AAAA,MACjC;AAAA,MACA,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,SAAS,IAAI;AAAA,MACvC,OAAO;AAAA,MACP,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,SAAK,aAAa,QAAQ,GAAG,QAAQ,CAAC,UAAU;AAC9C,YAAM,OAAO,MAAM,SAAS,EAAE,KAAK;AACnC,UAAI,MAAM;AAAA,MAEV;AAAA,IACF,CAAC;AAED,SAAK,aAAa,QAAQ,GAAG,QAAQ,CAAC,UAAU;AAC9C,YAAM,OAAO,MAAM,SAAS,EAAE,KAAK;AACnC,UAAI,QAAQ,CAAC,KAAK,SAAS,qBAAqB,GAAG;AAAA,MAEnD;AAAA,IACF,CAAC;AAED,SAAK,aAAa,GAAG,QAAQ,CAAC,MAAM,WAAW;AAC7C,UAAI,CAAC,KAAK,aAAa,SAAS,KAAK,SAAS,MAAM;AAClD,gBAAQ,KAAK,yEAA+D,IAAI,YAAY,MAAM,EAAE;AAAA,MACtG;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,WAAW,KAAa,YAAY,KAAyB;AACxE,YAAQ,IAAI,uCAAkC,GAAG,gBAAgB;AACjE,UAAM,YAAY,KAAK,IAAI;AAE3B,WAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AACzC,UAAI,KAAK,gBAAgB,KAAK,aAAa,aAAa,MAAM;AAC5D,cAAM,IAAI;AAAA,UACR,oDAAoD,KAAK,aAAa,QAAQ,sBAAsB,GAAG;AAAA,QACzG;AAAA,MACF;AAEA,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,GAAI,EAAE,CAAC;AACvE,YAAI,SAAS,QAAQ;AACnB,kBAAQ,IAAI,kCAA6B,GAAG,sBAAsB,SAAS,MAAM,IAAI;AACrF,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AAEA,UAAM,IAAI,MAAM,kCAAkC,SAAS,kBAAkB,GAAG,cAAc;AAAA,EAChG;AAAA,EAEA,MAAa,OAAsB;AACjC,QAAI,KAAK,aAAa,CAAC,KAAK,gBAAgB,CAAC,KAAK,aAAa,KAAK;AAClE;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,UAAM,MAAM,KAAK,aAAa;AAC9B,YAAQ,IAAI,+DAAwD,GAAG,KAAK;AAE5E,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,eAAS,KAAK,WAAW,CAAC,QAAQ;AAChC,YAAI,KAAK;AACP,cAAI;AACF,qBAAS,KAAK,SAAS;AAAA,UACzB,QAAQ;AAAA,UAER;AAAA,QACF;AACA,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAED,SAAK,eAAe;AAAA,EACtB;AACF;;;AChGA,OAAOE,WAAU;AACjB,OAAOC,SAAQ;AACf,SAAS,kBAAkB;AAM3B,SAAS,kBAA0B;AACjC,MAAI,MAAM;AACV,SAAO,QAAQD,MAAK,QAAQ,GAAG,GAAG;AAChC,UAAM,UAAUA,MAAK,KAAK,KAAK,cAAc;AAC7C,QAAIC,IAAG,WAAW,OAAO,GAAG;AAC1B,UAAI;AACF,cAAM,MAAM,KAAK,MAAMA,IAAG,aAAa,SAAS,OAAO,CAAC;AACxD,YAAI,IAAI,SAAS,2BAA2B,IAAI,SAAS,cAAc;AACrE,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAMD,MAAK,QAAQ,GAAG;AAAA,EACxB;AACA,SAAOA,MAAK,QAAQ,WAAW,IAAI;AACrC;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACjB,UAA6B,CAAC;AAAA,EAC9B,OAAO,WAAW,QAAQ,IAAI,CAAC;AAAA,EAEhC,SAAS,QAA+B;AAC7C,QAAI,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,IAAI,GAAG;AACpD;AAAA,IACF;AACA,SAAK,QAAQ,KAAK,MAAM;AACxB,YAAQ,IAAI,kCAA2B,OAAO,IAAI,GAAG,OAAO,UAAU,MAAM,OAAO,OAAO,MAAM,EAAE,EAAE;AAAA,EACtG;AAAA,EAEA,MAAa,KAAK,YAAqD;AACrE,QAAI,OAAO,eAAe,YAAY,eAAe,MAAM;AACzD,WAAK,SAAS,UAAU;AACxB;AAAA,IACF;AAEA,UAAM,mBAAmB,WAAW,KAAK;AACzC,QAAI,CAAC,iBAAkB;AAGvB,QACE,iBAAiB,WAAW,GAAG,KAC/B,iBAAiB,WAAW,GAAG,KAC/B,iBAAiB,SAAS,GAAG,KAC7B,iBAAiB,SAAS,IAAI,GAC9B;AACA,YAAM,eAAeA,MAAK,QAAQ,QAAQ,IAAI,GAAG,gBAAgB;AACjE,YAAM,KAAK,aAAa,YAAY;AACpC;AAAA,IACF;AAGA,UAAM,kBAAkB;AAAA,MACtBA,MAAK,QAAQ,QAAQ,IAAI,GAAG,eAAe,WAAW,GAAG,gBAAgB,KAAK;AAAA,MAC9EA,MAAK,QAAQ,QAAQ,IAAI,GAAG,eAAe,WAAW,GAAG,gBAAgB,KAAK;AAAA,MAC9EA,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW,GAAG,gBAAgB,KAAK;AAAA,MAC/DA,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW,GAAG,gBAAgB,KAAK;AAAA,MAC/DA,MAAK,QAAQ,QAAQ,IAAI,GAAG,eAAe,WAAW,kBAAkB,UAAU;AAAA,MAClFA,MAAK,QAAQ,QAAQ,IAAI,GAAG,eAAe,WAAW,kBAAkB,UAAU;AAAA,MAClFA,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW,kBAAkB,UAAU;AAAA,MACnEA,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW,kBAAkB,UAAU;AAAA,IACrE;AAEA,eAAW,aAAa,iBAAiB;AACvC,UAAIC,IAAG,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,aAAa,SAAS;AACjC;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,gBAAgB;AAChC,UAAM,oBAAoB;AAAA,MACxBD,MAAK,KAAK,SAAS,OAAO,WAAW,kBAAkB,UAAU;AAAA,MACjEA,MAAK,KAAK,SAAS,OAAO,WAAW,kBAAkB,UAAU;AAAA,MACjEA,MAAK,KAAK,SAAS,QAAQ,WAAW,kBAAkB,UAAU;AAAA,MAClEA,MAAK,KAAK,SAAS,WAAW,kBAAkB,UAAU;AAAA,MAC1DA,MAAK,KAAK,SAAS,WAAW,kBAAkB,UAAU;AAAA,MAC1DA,MAAK,QAAQ,WAAW,iBAAiB,kBAAkB,UAAU;AAAA,MACrEA,MAAK,QAAQ,WAAW,iBAAiB,kBAAkB,UAAU;AAAA,MACrEA,MAAK,QAAQ,WAAW,cAAc,kBAAkB,UAAU;AAAA,MAClEA,MAAK,QAAQ,WAAW,cAAc,kBAAkB,UAAU;AAAA,IACpE;AAEA,eAAW,WAAW,mBAAmB;AACvC,UAAIC,IAAG,WAAW,OAAO,GAAG;AAC1B,cAAM,KAAK,aAAa,OAAO;AAC/B;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,KAAK,OAAO,gBAAgB;AACnD,YAAM,SAA0B,IAAI,WAAW,IAAI,UAAU;AAC7D,UAAI,UAAU,OAAO,MAAM;AACzB,aAAK,SAAS,MAAM;AAAA,MACtB,OAAO;AACL,gBAAQ,KAAK,kCAAwB,gBAAgB,2CAA2C;AAAA,MAClG;AAAA,IACF,SAAS,KAAU;AACjB,cAAQ,KAAK,gDAAsC,gBAAgB,MAAM,IAAI,OAAO,EAAE;AAAA,IACxF;AAAA,EACF;AAAA,EAEA,MAAc,aAAa,UAAiC;AAC1D,QAAI,CAACA,IAAG,WAAW,QAAQ,GAAG;AAC5B,cAAQ,KAAK,gDAAsC,QAAQ,EAAE;AAC7D;AAAA,IACF;AAEA,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,KAAK,OAAO,QAAQ;AAC3C,YAAM,SAA0B,IAAI,WAAW,IAAI,UAAU;AAC7D,UAAI,UAAU,OAAO,MAAM;AACzB,aAAK,SAAS,MAAM;AAAA,MACtB,OAAO;AACL,gBAAQ,KAAK,+BAAqB,QAAQ,uDAAuD;AAAA,MACnG;AAAA,IACF,SAAS,KAAU;AACjB,cAAQ,MAAM,gDAA2C,QAAQ,KAAK,IAAI,OAAO;AAAA,IACnF;AAAA,EACF;AAAA,EAEA,MAAa,QAAQ,OAAoD;AACvE,eAAW,QAAQ,OAAO;AACxB,YAAM,KAAK,KAAK,IAAI;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAa,SAAS,aAA+C;AACnE,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,MAAO,OAAM,EAAE,MAAM,WAAW;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAa,cACX,SACA,aACyC;AACzC,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,eAAe;AACnB,cAAM,UAAU,MAAM,EAAE,cAAc,SAAS,WAAW;AAC1D,YAAI,SAAS;AACX,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,oBAAoB,SAAyB,aAA+C;AACvG,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,iBAAkB,OAAM,EAAE,iBAAiB,SAAS,WAAW;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,MAAa,iBAAiB,MAAY,SAAyB,aAA+C;AAChH,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,cAAe,OAAM,EAAE,cAAc,MAAM,SAAS,WAAW;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,MAAa,cAAc,KAAkB,MAAY,aAA+C;AACtG,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,eAAe;AACnB,cAAM,aAAa,MAAM,EAAE,cAAc,KAAK,MAAM,WAAW;AAC/D,YAAI,cAAc,OAAO,eAAe,UAAU;AAChD,iBAAO,OAAO,KAAK,UAAU;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAa,cAAc,YAAwB,aAA+C;AAChG,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,WAAY,OAAM,EAAE,WAAW,YAAY,WAAW;AAAA,IAC9D;AAAA,EACF;AAAA,EAEA,MAAa,YAAY,aAA+C;AACtE,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,UAAU;AACd,YAAI;AACF,gBAAM,EAAE,SAAS,WAAW;AAAA,QAC9B,SAAS,GAAQ;AACf,kBAAQ,MAAM,kCAAwB,EAAE,IAAI,cAAc,EAAE,OAAO;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAW,gBAAmC;AAC5C,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AACF;;;ACpMO,SAAS,gBAAgB,MAAY,SAAkB,WAAsC;AAClG,SAAO,OAAO,UAAiC;AAE7C,QAAI,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU,GAAG;AAC/D,YAAM,KAAK,KAAK,OAAO,EAAE,WAAW,mBAAmB,CAAC;AACxD,YAAM,KAAK,eAAe,GAAG;AAC7B;AAAA,IACF;AAGA,QAAI,MAAM,WAAW,GAAG,GAAG;AACzB,YAAM,KAAK,SAAS,CAAC,MAAM;AAAE,eAAO,SAAS,OAAO;AAAA,MAAG,GAAG,KAAK;AAC/D,YAAM,KAAK,eAAe,GAAG;AAC7B;AAAA,IACF;AAGA,QAAI,WAAW,WAAW;AACxB,UAAI;AACF,cAAM,UAAU,IAAI,IAAI,OAAO,OAAO,EAAE,SAAS;AACjD,cAAM,KAAK,KAAK,SAAS,EAAE,WAAW,mBAAmB,CAAC;AAC1D,cAAM,KAAK,eAAe,GAAG;AAC7B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,UAAM,YAAY,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI,KAAK;AAC3D,UAAM,KAAK,SAAS,CAAC,MAAM;AACzB,UAAI,OAAO,SAAS,SAAS,QAAW;AACtC,eAAO,SAAS,OAAO;AAAA,MACzB;AAAA,IACF,GAAG,SAAS;AACZ,UAAM,KAAK,eAAe,GAAG;AAAA,EAC/B;AACF;;;AC7CA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAOV,SAAS,oBACd,eACA,sBACA,YACA,WACoB;AACpB,MAAI;AACF,UAAM,YAAYA,MAAK,KAAK,eAAe,QAAQ;AACnD,QAAID,IAAG,WAAW,SAAS,GAAG;AAC5B,MAAAA,IAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACvD;AACA,IAAAA,IAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAG3C,QAAIA,IAAG,WAAW,UAAU,GAAG;AAC7B,MAAAA,IAAG,aAAa,YAAYC,MAAK,KAAK,WAAW,WAAW,CAAC;AAAA,IAC/D;AAGA,UAAM,cAAcA,MAAK,KAAK,sBAAsB,eAAe;AACnE,QAAID,IAAG,WAAW,WAAW,GAAG;AAC9B,MAAAA,IAAG,aAAa,aAAaC,MAAK,KAAK,WAAW,eAAe,CAAC;AAAA,IACpE;AAGA,eAAW,QAAQ,WAAW;AAC5B,UAAI,KAAK,YAAYD,IAAG,WAAW,KAAK,QAAQ,GAAG;AACjD,QAAAA,IAAG,aAAa,KAAK,UAAUC,MAAK,KAAK,WAAW,KAAK,QAAQ,CAAC;AAAA,MACpE;AAAA,IACF;AAEA,WAAOA,MAAK,KAAK,WAAW,WAAW;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACnBO,SAAS,iBAAiB,SAG/B;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,kBAAkB,EAAE,GAAG,gBAAgB;AAE3C,QAAM,MAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA,SAAS,OAAO,MAAc,SAA0B;AACtD,cAAQ,IAAI,wBAAiB,IAAI,KAAK,gBAAgB,KAAK,IAAI,gBAAgB,MAAM,GAAG;AACxF,aAAO,MAAM,cAAc,aAAa,MAAM,MAAM,iBAAiB,IAAI;AAAA,IAC3E;AAAA,IAEA,cAAc,OAAO,MAAc,SAA8B;AAC/D,cAAQ,IAAI,qBAAc,IAAI,eAAe,KAAK,UAAU,iBAAiB,KAAK,cAAc,EAAE,KAAK;AACvG,aAAO,MAAM,cAAc,UAAU,MAAM,MAAM,iBAAiB,IAAI;AAAA,IACxE;AAAA;AAAA,IAGA,UAAU,OAAO,UAAkB;AACjC,cAAQ,IAAI,wBAAiB,KAAK,EAAE;AACpC,YAAM,WAAW,KAAK;AAAA,IACxB;AAAA;AAAA,IAGA,QAAQ,OAAO,OAAe,WAAmB;AAC/C,cAAQ,IAAI,sBAAe,KAAK,IAAI,MAAM,EAAE;AAC5C,wBAAkB,EAAE,OAAO,OAAO;AAClC,UAAI,kBAAkB;AACtB,YAAM,KAAK,gBAAgB,eAAe;AAC1C,YAAM,KAAK,eAAe,GAAG;AAAA,IAC/B;AAAA,IAEA,WAAW,OAAO,WAA2B;AAC3C,cAAQ,IAAI,sBAAe,OAAO,IAAI,KAAK,OAAO,KAAK,IAAI,OAAO,MAAM,GAAG;AAC3E,YAAM,IAAI,OAAO,OAAO,OAAO,OAAO,MAAM;AAAA,IAC9C;AAAA,IAEA,aAAa,OAAO,UAAmB,UAAU,MAAM;AACrD,UAAI;AACJ,UAAI,UAAU;AACZ,cAAM,KAAK,MAAM,KAAK,EAAE,QAAQ;AAChC,YAAI,IAAI;AACN,wBAAc,MAAM,GAAG,YAAY;AAAA,QACrC;AAAA,MACF,OAAO;AACL,sBAAc,MAAM,KAAK,SAAS,OAAO;AAAA,UACvC,OAAO,SAAS,gBAAgB;AAAA,UAChC,QAAQ,SAAS,gBAAgB;AAAA,QACnC,EAAE;AAAA,MACJ;AAEA,UAAI,aAAa;AACf,cAAM,WAAW,KAAK,KAAK,YAAY,KAAK,IAAI,UAAU;AAC1D,cAAM,YAAY,KAAK,KAAK,YAAY,MAAM,IAAI,UAAU;AAC5D,gBAAQ,IAAI,2BAAoB,YAAY,MAAM,OAAO,QAAQ,IAAI,SAAS,EAAE;AAChF,cAAM,IAAI,OAAO,UAAU,SAAS;AAAA,MACtC,OAAO;AACL,gBAAQ,IAAI,sCAA4B,QAAQ,oCAAoC;AAAA,MACtF;AAAA,IACF;AAAA;AAAA,IAGA,MAAM,OAAO,OAAe;AAC1B,YAAM,KAAK,eAAe,EAAE;AAAA,IAC9B;AAAA,IAEA,iBAAiB,OAAO,UAAkB,YAAY,QAAS;AAC7D,YAAM,KAAK,gBAAgB,UAAU,EAAE,SAAS,UAAU,CAAC;AAAA,IAC7D;AAAA;AAAA,IAGA,OAAO,OAAO,aAAqB;AACjC,cAAQ,IAAI,2BAAe,QAAQ,EAAE;AACrC,YAAM,KAAK,MAAM,QAAQ;AAAA,IAC3B;AAAA,IAEA,YAAY,OAAO,aAAqB;AACtC,cAAQ,IAAI,gCAAoB,QAAQ,EAAE;AAC1C,YAAM,KAAK,MAAM,UAAU,EAAE,QAAQ,QAAQ,CAAC;AAAA,IAChD;AAAA,IAEA,MAAM,OAAO,UAAkB,SAAiB;AAC9C,cAAQ,IAAI,uBAAa,QAAQ,QAAQ,IAAI,GAAG;AAChD,YAAM,KAAK,KAAK,UAAU,IAAI;AAAA,IAChC;AAAA,IAEA,cAAc,OAAO,UAAkB,UAAkB;AACvD,cAAQ,IAAI,mBAAc,QAAQ,QAAQ,KAAK,GAAG;AAClD,YAAM,KAAK,aAAa,UAAU,KAAK;AAAA,IACzC;AAAA,IAEA,OAAO,OAAO,aAAqB;AACjC,cAAQ,IAAI,qBAAc,QAAQ,EAAE;AACpC,YAAM,KAAK,MAAM,QAAQ;AAAA,IAC3B;AAAA,IAEA,QAAQ,OAAO,UAAkB,WAAmB;AAClD,cAAQ,IAAI,sBAAe,QAAQ,OAAO,MAAM,IAAI;AACpD,YAAM,KAAK;AAAA,QACT,CAAC,EAAE,KAAK,GAAG,MAAM;AACf,gBAAM,KAAK,SAAS,cAAc,GAAG;AACrC,cAAI,IAAI;AACN,eAAG,aAAa;AAAA,UAClB,OAAO;AACL,mBAAO,SAAS,GAAG,EAAE;AAAA,UACvB;AAAA,QACF;AAAA,QACA,EAAE,KAAK,UAAU,IAAI,OAAO;AAAA,MAC9B;AACA,YAAM,KAAK,eAAe,GAAG;AAAA,IAC/B;AAAA;AAAA,IAGA,KAAK,CAAC,QAAgB;AACpB,cAAQ,IAAI,2BAAiB,GAAG,EAAE;AAAA,IACpC;AAAA;AAAA,IAGA,YAAY,OAAO,QAAgB,MAAW,gBAAsB;AAClE,cAAQ,IAAI,kDAAwC,MAAM,yCAAyC;AAAA,IACrG;AAAA;AAAA,IAGA,cAAc,OACZ,KACA,MACA,iBACG;AACH,UAAI,CAAC,eAAe;AAClB,gBAAQ,IAAI,4EAAkE;AAC9E;AAAA,MACF;AACA,cAAQ,IAAI,uCAAgC,cAAc,UAAU,KAAK,IAAI,GAAG,OAAO,cAAc,UAAU,GAAG,EAAE;AACpH,YAAM,cAAc,aAAa;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,QAAQ,cAAc;AAAA,QACtB,QAAQ,cAAc;AAAA,QACtB,SAAS,cAAc;AAAA,QACvB,SAAS,cAAc;AAAA,MACzB,CAAC;AAAA,IACH;AAAA;AAAA,IAGA,kBAAkB,MAAM,eAAe,UAAU;AAAA,IACjD,oBAAoB,MAAM,eAAe,YAAY;AAAA,IACrD,kBAAkB,MAAM,eAAe;AAAA;AAAA,IAGvC,UAAU,OAAO,aAA6C;AAC5D,YAAM,OAAO,MAAM,KAAK,YAAY,QAAQ;AAC5C,aAAO,OAAO,KAAK,KAAK,IAAI;AAAA,IAC9B;AAAA,IAEA,aAAa,YAA6B;AACxC,aAAO,MAAM,KAAK,SAAS,MAAM,SAAS,KAAK,aAAa,EAAE;AAAA,IAChE;AAAA,IAEA,WAAW,OAAO,aAAqB;AACrC,UAAI;AACF,cAAM,UAAU,MAAM,KAAK,EAAE,QAAQ;AACrC,YAAI,CAAC,QAAS,QAAO;AACrB,eAAO,MAAM,QAAQ,UAAU;AAAA,MACjC,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,iBAAiB,OAAO,aAAqB;AAC3C,YAAM,WAAW,MAAM,KAAK,GAAG,QAAQ;AACvC,aAAO,SAAS;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,oBAAoB,MAAM;AAAA,EAC5B;AACF;;;AXxKA,eAAsB,kBAAkB,SAAyC;AAC/E,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,YAAY,KAAK,IAAI;AAE3B,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG,EAAE,MAAM,GAAG,EAAE;AAC5E,QAAM,gBAAgB,QAAQ,iBAAiBC,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW;AAEtF,MAAI,QAAQ,kBAAkBC,IAAG,WAAW,aAAa,GAAG;AAC1D,YAAQ,IAAI,iEAA0D,aAAa,KAAK;AACxF,UAAM,UAAUA,IAAG,YAAY,eAAe,EAAE,eAAe,KAAK,CAAC;AACrE,eAAW,SAAS,SAAS;AAC3B,MAAAA,IAAG,OAAOD,MAAK,KAAK,eAAe,MAAM,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAClF;AAAA,EACF;AAEA,QAAM,uBAAuBA,MAAK,KAAK,eAAe,GAAG,SAAS,EAAE,IAAI,SAAS,EAAE;AACnF,MAAI,CAACC,IAAG,WAAW,oBAAoB,GAAG;AACxC,IAAAA,IAAG,UAAU,sBAAsB,EAAE,WAAW,KAAK,CAAC;AAAA,EACxD;AAEA,QAAM,gBAAgB,IAAI,cAAc,oBAAoB;AAC5D,QAAM,iBAAiB,IAAI,eAAe;AAC1C,QAAM,kBAAkB,SAAS,YAAY,CAAC,KAAK,iBAAiB;AACpE,QAAM,kBAAkB,EAAE,OAAO,gBAAgB,OAAO,QAAQ,gBAAgB,OAAO;AAEvF,MAAI,gBAAsC;AAC1C,MAAI,iBAAwC;AAC5C,MAAI,mBAAiD;AAGrD,QAAM,gBAAgB,IAAI,cAAc;AACxC,QAAM,cAAc,CAAC,GAAI,QAAQ,WAAW,CAAC,GAAI,GAAI,SAAS,WAAW,CAAC,CAAE;AAC5E,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,cAAc,QAAQ,WAAW;AAAA,EACzC;AAEA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,OAAO,oBAAI,IAAqB;AAAA,EAClC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI;AAEJ,YAAQ,IAAI;AAAA,yCAA4C;AACxD,YAAQ,IAAI,mCAA4B,SAAS,KAAK,EAAE;AACxD,YAAQ,IAAI,mBAAY,WAAW,YAAY,CAAC,EAAE;AAClD,YAAQ,IAAI,wBAAiB,oBAAoB,EAAE;AACnD,YAAQ,IAAI;AAAA,CAA4C;AAGxD,QAAI,QAAQ,cAAc;AACxB,uBAAiB,IAAI,eAAe;AACpC,YAAM,eAAe,MAAM,QAAQ,cAAc,EAAE,KAAK,QAAQ,SAAS,CAAC;AAC1E,YAAM,aAAa,QAAQ,OAAO;AAClC,YAAM,eAAe,WAAW,UAAU;AAAA,IAC5C;AAGA,UAAM,cAAc,SAAS,WAAW;AAExC,QAAI,OAAO,SAAS,UAAU,YAAY;AACxC,cAAQ,IAAI,oDAA6C;AACzD,YAAM,SAAS,MAAM;AAAA,IACvB;AAGA,UAAM,gBAAgB,MAAM,cAAc;AAAA,MACxC,EAAE,iBAAiB,QAAQ,QAAQ,OAAO;AAAA,MAC1C;AAAA,IACF;AAEA,QAAI,eAAe;AACjB,aAAO,cAAc;AACrB,gBAAU,cAAc;AACxB,UAAI,cAAc,MAAM;AACtB,2BAAmB,cAAc;AAAA,MACnC;AAAA,IACF,WAAW,eAAe,WAAW;AACnC,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF,OAAO;AAEL,sBAAgB,IAAI,cAAc;AAAA,QAChC,YAAY,QAAQ;AAAA,QACpB,KAAK,QAAQ;AAAA,QACb,QAAQ,QAAQ;AAAA,MAClB,CAAC;AAED,UAAI,SAAS,cAAc,SAAS,WAAW,SAAS,GAAG;AACzD,gBAAQ,IAAI,oCAA6B,SAAS,WAAW,MAAM,gBAAgB;AACnF,cAAM,cAAc,gBAAgB,SAAS,UAAU;AAAA,MACzD;AAEA,YAAM,MAAM,MAAM,cAAc,MAAM,eAAe;AACrD,aAAO,IAAI;AACX,gBAAU,IAAI;AAEd,UAAI,SAAS,cAAc,SAAS,WAAW,SAAS,GAAG;AACzD,cAAM,cAAc,gBAAgB,SAAS,UAAU;AAAA,MACzD;AAAA,IACF;AAGA,UAAM,cAAc,oBAAoB,SAAS,WAAW;AAC5D,UAAM,cAAc,iBAAiB,MAAM,SAAS,WAAW;AAG/D,mBAAe,OAAO,IAAI;AAC1B,YAAQ,IAAI;AAAA,CAAwE;AAGpF,UAAM,aAAa,gBAAgB,MAAM,eAAe,SAAS,QAAQ,GAAG;AAE5E,QAAI,SAAS,OAAO;AAClB,cAAQ,IAAI,mDAA4C,SAAS,KAAK,EAAE;AACxE,YAAM,WAAW,SAAS,KAAK;AAAA,IACjC;AAEA,UAAM,EAAE,IAAI,IAAI,iBAAiB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAGD,UAAM,cAAc,cAAc,KAAK,MAAM,WAAW;AAGxD,UAAM,SAAS,IAAI,GAAG;AAEtB,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,YAAY,cAAc,aAAa;AAC7C,UAAM,gBAAgB,eAAe,UAAU;AAC/C,UAAM,kBAAkB,eAAe,YAAY;AAEnD,QAAI,cAAc,SAAS,GAAG;AAC5B,cAAQ,IAAI;AAAA,4BAAwB,cAAc,MAAM,EAAE;AAC1D,oBAAc,QAAQ,CAAC,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AAAA,IACzF;AACA,QAAI,gBAAgB,SAAS,GAAG;AAC9B,cAAQ,IAAI;AAAA,8BAA0B,gBAAgB,MAAM,EAAE;AAAA,IAChE;AAGA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,gBAAgB,CAAC;AAAA,IACnB;AAEA,UAAM,cAAc,cAAc,YAAY,WAAW;AACzD,UAAM,aAAa,eAAe,eAAe,UAAU;AAC3D,UAAM,eAAe,oBAAoB,eAAe,sBAAsB,YAAY,SAAS;AAEnG,YAAQ,IAAI;AAAA,2CAAyC;AACrD,YAAQ,IAAI,iCAA0B,UAAU,MAAM,EAAE;AACxD,YAAQ,IAAI,6BAAsB,cAAc,MAAM,EAAE;AACxD,YAAQ,IAAI,+BAAwB,gBAAgB,MAAM,EAAE;AAC5D,YAAQ,IAAI,8BAAuB,UAAU,EAAE;AAC/C,QAAI,cAAc;AAChB,cAAQ,IAAI,4BAAqB,YAAY,EAAE;AAAA,IACjD;AAEA,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB;AAAA,MACA,SAAS;AAAA,MACT,gBAAgB,UAAU;AAAA,MAC1B,eAAe,cAAc;AAAA,MAC7B,iBAAiB,gBAAgB;AAAA,MACjC;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF,SAAS,KAAU;AACjB,YAAQ,MAAM;AAAA,6BAA2B,IAAI,OAAO,EAAE;AACtD,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,YAAY,cAAc,aAAa;AAC7C,UAAM,gBAAgB,eAAe,UAAU;AAC/C,UAAM,kBAAkB,eAAe,YAAY;AAEnD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa,eAAe,eAAe,UAAU;AAC3D,wBAAoB,eAAe,sBAAsB,YAAY,SAAS;AAE9E,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB;AAAA,MACA,SAAS;AAAA,MACT,gBAAgB,UAAU;AAAA,MAC1B,eAAe,cAAc;AAAA,MAC7B,iBAAiB,gBAAgB;AAAA,MACjC;AAAA,MACA,cAAc;AAAA,MACd,OAAO,IAAI;AAAA,IACb;AAAA,EACF,UAAE;AAEA,UAAM,cAAc,YAAY,WAAW;AAE3C,QAAI,OAAO,SAAS,aAAa,YAAY;AAC3C,UAAI;AACF,gBAAQ,IAAI,0DAAmD;AAC/D,cAAM,SAAS,SAAS;AAAA,MAC1B,SAAS,aAAkB;AACzB,gBAAQ,MAAM,2DAAiD,YAAY,OAAO;AAAA,MACpF;AAAA,IACF;AAEA,QAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAAG;AACvD,iBAAW,eAAe,QAAQ,YAAY;AAC5C,YAAI;AACF,gBAAM,oBAAoBD,MAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW;AACjE,cAAIC,IAAG,WAAW,iBAAiB,GAAG;AACpC,oBAAQ,IAAI,sCAA+B,iBAAiB,EAAE;AAC9D,YAAAA,IAAG,OAAO,mBAAmB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,UAC/D;AAAA,QACF,SAAS,UAAe;AACtB,kBAAQ,MAAM,gDAAsC,WAAW,KAAK,SAAS,OAAO;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,QAAQ;AACnB,UAAI,iBAAkB,OAAM,iBAAiB;AAC7C,UAAI,cAAe,OAAM,cAAc,KAAK;AAC5C,UAAI,eAAgB,OAAM,eAAe,KAAK;AAAA,IAChD,OAAO;AACL,cAAQ,IAAI,kEAA2D;AAAA,IACzE;AAAA,EACF;AACF;;;AYpTA,OAAOC,WAAU;AACjB,OAAOC,SAAQ;AAyBf,IAAM,aAA6C;AAAA,EAEjD,SAAS,iBAAiB;AAAA,EAC1B,SAAS,iBAAiB;AAAA,EAC1B,KAAK,iBAAiB;AAAA,EACtB,iBAAiB,iBAAiB;AAAA,EAClC,MAAM,iBAAiB;AAAA,EACvB,QAAQ,EAAE,MAAM,UAAU,OAAO,KAAK,QAAQ,IAAI;AAAA,EAClD,QAAQ,EAAE,MAAM,UAAU,OAAO,KAAK,QAAQ,KAAK;AAAA,EACnD,WAAW,iBAAiB;AAC9B;AAEA,SAAS,qBAAqB,KAAkC;AAC9D,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG;AAC5B,WAAO,CAAC,iBAAiB,SAAS,WAAW,MAAM;AAAA,EACrD;AAEA,QAAM,UAA4B,CAAC;AACnC,aAAW,QAAQ,KAAK;AACtB,UAAM,QAAQ,KAAK,YAAY,EAAE,KAAK;AACtC,QAAI,WAAW,KAAK,GAAG;AACrB,cAAQ,KAAK,WAAW,KAAK,CAAC;AAAA,IAChC,WAAW,MAAM,SAAS,GAAG,GAAG;AAC9B,YAAM,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC1D,UAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;AAC1B,gBAAQ,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,GAAG,QAAQ,EAAE,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,QAAQ,SAAS,IAAI,UAAU,CAAC,iBAAiB,SAAS,WAAW,MAAM;AACpF;AAEA,IAAM,kBAAkB,CAAC,MAAM,KAAM,MAAM,MAAM,MAAM,KAAM,IAAI;AAEjE,eAAe,iBAAiB,KAA+B;AAC7D,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,GAAG,EAAE,CAAC;AACjE,WAAO,QAAQ,IAAI,MAAM;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,wBAAgD;AAC7D,aAAW,QAAQ,iBAAiB;AAClC,UAAM,MAAM,oBAAoB,IAAI;AACpC,QAAI,MAAM,iBAAiB,GAAG,GAAG;AAC/B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,aAAa,SAA2C;AAC5E,MAAI,YAAY,QAAQ;AACxB,MAAI,mBAAmB;AACvB,MAAI;AAGJ,MAAI,CAAC,aAAa,CAAC,QAAQ,OAAO;AAChC,UAAM,YAAY,MAAM,sBAAsB;AAC9C,QAAI,WAAW;AACb,kBAAY;AACZ,cAAQ,IAAI,wDAAiD,SAAS,EAAE;AAAA,IAC1E,OAAO;AAEL,YAAM,eAAe,kBAAkB;AACvC,UAAIC,IAAG,WAAW,YAAY,KAAKA,IAAG,WAAWC,MAAK,KAAK,cAAc,YAAY,CAAC,GAAG;AACvF,2BAAmB;AACnB,qBAAa;AACb,gBAAQ,IAAI,sFAA+E,YAAY,gCAAgC;AAAA,MACzI,OAAO;AACL,gBAAQ,MAAM;AAAA,8DAA4D,gBAAgB,KAAK,IAAI,CAAC,+BAA+B;AACnI,gBAAQ,IAAI,8BAAuB;AACnC,gBAAQ,IAAI,wEAAwE;AACpF,gBAAQ,IAAI,8EAA8E;AAC1F,gBAAQ,IAAI;AAAA,CAA8C;AAC1D,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,WAAW,CAAC,aAAa,QAAQ,OAAO;AACtC,gBAAY;AAAA,EACd;AAGA,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,iBAAiB,QAAQ,QAAQ;AACvC,QAAM,kBAAkB,qBAAqB,QAAQ,SAAS;AAE9D,UAAQ,IAAI;AAAA,6DAAyD,aAAa,UAAU,EAAE;AAC9F,UAAQ,IAAI,kCAA2B,gBAAgB,MAAM,eAAe,gBAAgB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE;AACzJ,MAAI,QAAQ,UAAU;AACpB,YAAQ,IAAI,qDAA8C,QAAQ,QAAQ,GAAG;AAAA,EAC/E;AAEA,QAAM,eAAe,iBAAiB;AAAA,IACpC,IAAI;AAAA,IACJ,OAAO,uBAAuB,aAAa,gBAAgB;AAAA,IAC3D,aAAa;AAAA,IACb,OAAO,aAAa;AAAA,IACpB,WAAW;AAAA,IACX,KAAK,OAAO,QAAQ;AAClB,UAAI,IAAI,WAAW,MAAM,8BAA8B;AACvD,YAAM,IAAI,KAAK,MAAM;AAErB,eAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,cAAM,KAAK,gBAAgB,CAAC;AAC5B,cAAM,UAAU,OAAO,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC7C,YAAI,IAAI,0BAA0B,GAAG,IAAI,KAAK,GAAG,KAAK,IAAI,GAAG,MAAM,GAAG;AACtE,cAAM,IAAI,UAAU,EAAE;AACtB,cAAM,IAAI,KAAK,GAAG;AAClB,cAAM,IAAI,QAAQ,GAAG,OAAO,IAAI,cAAc,IAAI,GAAG,IAAI,IAAI,EAAE,UAAU,QAAQ,SAAS,CAAC;AAAA,MAC7F;AAEA,UAAI,QAAQ,UAAU;AACpB,YAAI,IAAI,0BAA0B,QAAQ,QAAQ,GAAG;AACrD,YAAI;AACF,gBAAM,IAAI,UAAU,iBAAiB,OAAO;AAC5C,gBAAM,IAAI,YAAY,QAAQ,UAAU,EAAE;AAC1C,gBAAM,IAAI,QAAQ,MAAM,cAAc,kBAAkB,EAAE,UAAU,QAAQ,SAAS,CAAC;AAAA,QACxF,SAAS,KAAU;AACjB,cAAI,IAAI,4CAAkC,QAAQ,QAAQ,MAAM,IAAI,OAAO,EAAE;AAAA,QAC/E;AAAA,MACF;AAEA,YAAM,SAAS,IAAI,iBAAiB;AACpC,UAAI,OAAO,SAAS,GAAG;AACrB,YAAI,IAAI,oBAAa,OAAO,MAAM,oCAAoC;AAAA,MACxE,OAAO;AACL,YAAI,IAAI,+CAA0C;AAAA,MACpD;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,SAAS,MAAM,kBAAkB;AAAA,IACrC,UAAU;AAAA,IACV,YAAY,QAAQ,QAAQ;AAAA,IAC5B,KAAK,mBAAmB,SAAY;AAAA,IACpC,YAAY,mBAAmB,aAAa;AAAA,IAC5C,cAAc,QAAQ;AAAA,IACtB,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,IACxB,YAAY,QAAQ;AAAA,IAChB,gBAAgB,QAAQ;AAAA,IAC5B,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,eAAe,QAAQ,SAASA,MAAK,QAAQ,QAAQ,IAAI,GAAG,QAAQ,MAAM,IAAI;AAAA,IAC9E,SAAS,QAAQ;AAAA,EACnB,CAAC;AAGD,UAAQ,IAAI;AAAA,yCAA4C;AACxD,UAAQ,IAAI,gCAAyB;AACrC,UAAQ,IAAI,wBAAiB,OAAO,cAAc,EAAE;AACpD,UAAQ,IAAI,6BAAsB,OAAO,aAAa,EAAE;AACxD,UAAQ,IAAI,+BAAwB,OAAO,eAAe,EAAE;AAC5D,UAAQ,IAAI,qBAAc,OAAO,UAAU,EAAE;AAC7C,UAAQ,IAAI;AAAA,CAA4C;AAExD,SAAO,OAAO,WAAW,OAAO,kBAAkB;AACpD;","names":["path","fs","fs","path","fs","path","path","fs","fs","path","path","fs","fs","path","path","fs","path","fs","fs","path"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { A as AgentLensPlugin } from '../../dsl-BIjVN1M0.mjs';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
interface DumpA11yOptions {
|
|
5
|
+
compact?: boolean;
|
|
6
|
+
selector?: string;
|
|
7
|
+
saveToFile?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare const a11yTreePlugin: AgentLensPlugin;
|
|
10
|
+
|
|
11
|
+
export { type DumpA11yOptions, a11yTreePlugin, a11yTreePlugin as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { A as AgentLensPlugin } from '../../dsl-BIjVN1M0.js';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
interface DumpA11yOptions {
|
|
5
|
+
compact?: boolean;
|
|
6
|
+
selector?: string;
|
|
7
|
+
saveToFile?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare const a11yTreePlugin: AgentLensPlugin;
|
|
10
|
+
|
|
11
|
+
export { type DumpA11yOptions, a11yTreePlugin, a11yTreePlugin as default };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/plugins/a11y-tree/index.ts
|
|
31
|
+
var a11y_tree_exports = {};
|
|
32
|
+
__export(a11y_tree_exports, {
|
|
33
|
+
a11yTreePlugin: () => a11yTreePlugin,
|
|
34
|
+
default: () => a11y_tree_default
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(a11y_tree_exports);
|
|
37
|
+
var import_fs = __toESM(require("fs"));
|
|
38
|
+
var import_path = __toESM(require("path"));
|
|
39
|
+
|
|
40
|
+
// src/shared/api/plugin.ts
|
|
41
|
+
function definePlugin(plugin) {
|
|
42
|
+
return plugin;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/plugins/a11y-tree/formatter.ts
|
|
46
|
+
function formatAccessibilityTree(node, depth = 0, options) {
|
|
47
|
+
if (!node) return "";
|
|
48
|
+
const indent = " ".repeat(depth);
|
|
49
|
+
const parts = [`${indent}- role: **${node.role}**`];
|
|
50
|
+
if (node.name) {
|
|
51
|
+
parts.push(`"${node.name.replace(/"/g, '\\"')}"`);
|
|
52
|
+
}
|
|
53
|
+
const flags = [];
|
|
54
|
+
if (node.level !== void 0) flags.push(`level ${node.level}`);
|
|
55
|
+
if (node.disabled) flags.push("disabled");
|
|
56
|
+
if (node.required) flags.push("required");
|
|
57
|
+
if (node.focused) flags.push("focused");
|
|
58
|
+
if (node.checked !== void 0 && node.checked !== false) flags.push(`checked: ${String(node.checked)}`);
|
|
59
|
+
if (node.pressed !== void 0) flags.push(`pressed: ${String(node.pressed)}`);
|
|
60
|
+
if (node.selected) flags.push("selected");
|
|
61
|
+
if (node.value !== void 0 && node.value !== "") flags.push(`value: "${String(node.value)}"`);
|
|
62
|
+
if (flags.length > 0) {
|
|
63
|
+
parts.push(`[${flags.join(", ")}]`);
|
|
64
|
+
}
|
|
65
|
+
let result = parts.join(" ") + "\n";
|
|
66
|
+
if (node.children && node.children.length > 0) {
|
|
67
|
+
for (const child of node.children) {
|
|
68
|
+
if (options?.compact && !child.name && (!child.children || child.children.length === 0)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
result += formatAccessibilityTree(child, depth + 1, options);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/plugins/a11y-tree/index.ts
|
|
78
|
+
var STATE_A11Y_KEY = "latestA11yTree";
|
|
79
|
+
async function extractDomAccessibilityTree(page, rootSelector) {
|
|
80
|
+
return await page.evaluate((selector) => {
|
|
81
|
+
function getRole(el) {
|
|
82
|
+
const explicit = el.getAttribute("role");
|
|
83
|
+
if (explicit) return explicit;
|
|
84
|
+
const tag = el.tagName.toLowerCase();
|
|
85
|
+
if (tag === "button") return "button";
|
|
86
|
+
if (tag === "a" && el.hasAttribute("href")) return "link";
|
|
87
|
+
if (tag === "input") {
|
|
88
|
+
const type = el.type || "text";
|
|
89
|
+
if (["checkbox", "radio"].includes(type)) return type;
|
|
90
|
+
if (["button", "submit", "reset"].includes(type)) return "button";
|
|
91
|
+
return "textbox";
|
|
92
|
+
}
|
|
93
|
+
if (tag === "textarea") return "textbox";
|
|
94
|
+
if (tag === "select") return "combobox";
|
|
95
|
+
if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) return "heading";
|
|
96
|
+
if (tag === "nav") return "navigation";
|
|
97
|
+
if (tag === "main") return "main";
|
|
98
|
+
if (tag === "header") return "banner";
|
|
99
|
+
if (tag === "footer") return "contentinfo";
|
|
100
|
+
if (tag === "ul" || tag === "ol") return "list";
|
|
101
|
+
if (tag === "li") return "listitem";
|
|
102
|
+
if (tag === "table") return "table";
|
|
103
|
+
if (tag === "img") return "img";
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
function getName(el) {
|
|
107
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
108
|
+
if (ariaLabel) return ariaLabel.trim();
|
|
109
|
+
const placeholder = el.placeholder;
|
|
110
|
+
if (placeholder) return placeholder.trim();
|
|
111
|
+
const alt = el.getAttribute("alt");
|
|
112
|
+
if (alt) return alt.trim();
|
|
113
|
+
const tag = el.tagName.toLowerCase();
|
|
114
|
+
if (["button", "a", "h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) {
|
|
115
|
+
return (el.innerText || el.textContent || "").trim().slice(0, 80);
|
|
116
|
+
}
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
function walk(el) {
|
|
120
|
+
const htmlEl = el;
|
|
121
|
+
if (!htmlEl || htmlEl.nodeType !== 1) return null;
|
|
122
|
+
const style = window.getComputedStyle(htmlEl);
|
|
123
|
+
if (style.display === "none" || style.visibility === "hidden") return null;
|
|
124
|
+
const role = getRole(htmlEl);
|
|
125
|
+
const name = getName(htmlEl);
|
|
126
|
+
const children = [];
|
|
127
|
+
for (const child of Array.from(htmlEl.children)) {
|
|
128
|
+
const childNode = walk(child);
|
|
129
|
+
if (childNode) children.push(childNode);
|
|
130
|
+
}
|
|
131
|
+
if (!role && !name && children.length === 0) return null;
|
|
132
|
+
if (!role && !name) {
|
|
133
|
+
if (children.length === 1) return children[0];
|
|
134
|
+
return { role: "group", children };
|
|
135
|
+
}
|
|
136
|
+
const node = { role: role || "generic" };
|
|
137
|
+
if (name) node.name = name;
|
|
138
|
+
if (children.length > 0) node.children = children;
|
|
139
|
+
if (htmlEl.tagName.startsWith("H") && htmlEl.tagName.length === 2) {
|
|
140
|
+
node.level = parseInt(htmlEl.tagName[1], 10);
|
|
141
|
+
}
|
|
142
|
+
if (htmlEl.disabled) node.disabled = true;
|
|
143
|
+
if (htmlEl.hasAttribute("required")) node.required = true;
|
|
144
|
+
if (document.activeElement === htmlEl) node.focused = true;
|
|
145
|
+
return node;
|
|
146
|
+
}
|
|
147
|
+
const rootEl = selector ? document.querySelector(selector) : document.body;
|
|
148
|
+
return rootEl ? walk(rootEl) : null;
|
|
149
|
+
}, rootSelector);
|
|
150
|
+
}
|
|
151
|
+
var a11yTreePlugin = definePlugin({
|
|
152
|
+
name: "a11y-tree",
|
|
153
|
+
version: "1.0.0",
|
|
154
|
+
extendContext: (_ctx, page, hookContext) => {
|
|
155
|
+
return {
|
|
156
|
+
dumpAccessibilityTree: async (options) => {
|
|
157
|
+
console.log(`\u267F [a11y-tree] Capturing semantic accessibility tree...`);
|
|
158
|
+
const rootNode = await extractDomAccessibilityTree(page, options?.selector);
|
|
159
|
+
const markdown = formatAccessibilityTree(rootNode, 0, { compact: options?.compact ?? true });
|
|
160
|
+
if (hookContext.artifactsDir && options?.saveToFile !== false) {
|
|
161
|
+
const filePath = import_path.default.join(hookContext.artifactsDir, "a11y-tree.md");
|
|
162
|
+
import_fs.default.writeFileSync(filePath, markdown, "utf-8");
|
|
163
|
+
console.log(`\u267F [a11y-tree] Dump saved to: ${filePath}`);
|
|
164
|
+
}
|
|
165
|
+
hookContext.state.set(STATE_A11Y_KEY, markdown);
|
|
166
|
+
return markdown;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
onAfterRun: (reportData, hookContext) => {
|
|
171
|
+
const tree = hookContext.state.get(STATE_A11Y_KEY);
|
|
172
|
+
if (tree && reportData.customSections) {
|
|
173
|
+
const truncated = tree.length > 3e3 ? tree.slice(0, 3e3) + "\n\n*...and more (see a11y-tree.md)*" : tree;
|
|
174
|
+
reportData.customSections.push({
|
|
175
|
+
title: "\u267F Accessibility Semantic Tree (for Text AI Agents)",
|
|
176
|
+
content: `> Clean semantic hierarchy for text models (GPT-4o-mini, Haiku, Ollama).
|
|
177
|
+
|
|
178
|
+
\`\`\`markdown
|
|
179
|
+
${truncated}
|
|
180
|
+
\`\`\``
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
var a11y_tree_default = a11yTreePlugin;
|
|
186
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
187
|
+
0 && (module.exports = {
|
|
188
|
+
a11yTreePlugin
|
|
189
|
+
});
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/a11y-tree/index.ts","../../../src/shared/api/plugin.ts","../../../src/plugins/a11y-tree/formatter.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport type { Page } from 'playwright';\nimport { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport { formatAccessibilityTree, type AccessibilityNode } from './formatter';\n\nconst STATE_A11Y_KEY = 'latestA11yTree';\n\nexport interface DumpA11yOptions {\n compact?: boolean;\n selector?: string;\n saveToFile?: boolean;\n}\n\nasync function extractDomAccessibilityTree(page: Page, rootSelector?: string): Promise<AccessibilityNode | null> {\n return await page.evaluate((selector) => {\n function getRole(el: HTMLElement): string {\n const explicit = el.getAttribute('role');\n if (explicit) return explicit;\n const tag = el.tagName.toLowerCase();\n if (tag === 'button') return 'button';\n if (tag === 'a' && el.hasAttribute('href')) return 'link';\n if (tag === 'input') {\n const type = (el as HTMLInputElement).type || 'text';\n if (['checkbox', 'radio'].includes(type)) return type;\n if (['button', 'submit', 'reset'].includes(type)) return 'button';\n return 'textbox';\n }\n if (tag === 'textarea') return 'textbox';\n if (tag === 'select') return 'combobox';\n if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) return 'heading';\n if (tag === 'nav') return 'navigation';\n if (tag === 'main') return 'main';\n if (tag === 'header') return 'banner';\n if (tag === 'footer') return 'contentinfo';\n if (tag === 'ul' || tag === 'ol') return 'list';\n if (tag === 'li') return 'listitem';\n if (tag === 'table') return 'table';\n if (tag === 'img') return 'img';\n return '';\n }\n\n function getName(el: HTMLElement): string {\n const ariaLabel = el.getAttribute('aria-label');\n if (ariaLabel) return ariaLabel.trim();\n const placeholder = (el as HTMLInputElement).placeholder;\n if (placeholder) return placeholder.trim();\n const alt = el.getAttribute('alt');\n if (alt) return alt.trim();\n const tag = el.tagName.toLowerCase();\n if (['button', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) {\n return (el.innerText || el.textContent || '').trim().slice(0, 80);\n }\n return '';\n }\n\n function walk(el: Element): any {\n const htmlEl = el as HTMLElement;\n if (!htmlEl || htmlEl.nodeType !== 1) return null;\n\n const style = window.getComputedStyle(htmlEl);\n if (style.display === 'none' || style.visibility === 'hidden') return null;\n\n const role = getRole(htmlEl);\n const name = getName(htmlEl);\n\n const children: any[] = [];\n for (const child of Array.from(htmlEl.children)) {\n const childNode = walk(child);\n if (childNode) children.push(childNode);\n }\n\n if (!role && !name && children.length === 0) return null;\n\n if (!role && !name) {\n if (children.length === 1) return children[0];\n return { role: 'group', children };\n }\n\n const node: any = { role: role || 'generic' };\n if (name) node.name = name;\n if (children.length > 0) node.children = children;\n\n if (htmlEl.tagName.startsWith('H') && htmlEl.tagName.length === 2) {\n node.level = parseInt(htmlEl.tagName[1], 10);\n }\n if ((htmlEl as any).disabled) node.disabled = true;\n if (htmlEl.hasAttribute('required')) node.required = true;\n if (document.activeElement === htmlEl) node.focused = true;\n\n return node;\n }\n\n const rootEl = selector ? document.querySelector(selector) : document.body;\n return rootEl ? walk(rootEl) : null;\n }, rootSelector);\n}\n\nexport const a11yTreePlugin: AgentLensPlugin = definePlugin({\n name: 'a11y-tree',\n version: '1.0.0',\n\n extendContext: (_ctx, page, hookContext) => {\n return {\n dumpAccessibilityTree: async (options?: DumpA11yOptions): Promise<string> => {\n console.log(`♿ [a11y-tree] Capturing semantic accessibility tree...`);\n\n const rootNode = await extractDomAccessibilityTree(page, options?.selector);\n const markdown = formatAccessibilityTree(rootNode, 0, { compact: options?.compact ?? true });\n\n if (hookContext.artifactsDir && options?.saveToFile !== false) {\n const filePath = path.join(hookContext.artifactsDir, 'a11y-tree.md');\n fs.writeFileSync(filePath, markdown, 'utf-8');\n console.log(`♿ [a11y-tree] Dump saved to: ${filePath}`);\n }\n\n hookContext.state.set(STATE_A11Y_KEY, markdown);\n return markdown;\n }\n };\n },\n\n onAfterRun: (reportData, hookContext) => {\n const tree = hookContext.state.get(STATE_A11Y_KEY) as string | undefined;\n if (tree && reportData.customSections) {\n const truncated = tree.length > 3000 ? tree.slice(0, 3000) + '\\n\\n*...and more (see a11y-tree.md)*' : tree;\n reportData.customSections.push({\n title: '♿ Accessibility Semantic Tree (for Text AI Agents)',\n content: `> Clean semantic hierarchy for text models (GPT-4o-mini, Haiku, Ollama).\\n\\n\\`\\`\\`markdown\\n${truncated}\\n\\`\\`\\``\n });\n }\n }\n});\n\nexport default a11yTreePlugin;\n","import type { Page, BrowserContext, Browser } from 'playwright';\nimport type { TestContext, VisualScenario } from './dsl';\nimport type { ReportData } from '../types/report';\n\nexport interface DriverLaunchResult {\n page: Page;\n context: BrowserContext;\n browser?: Browser;\n stop?: () => Promise<void>;\n}\n\nexport interface PluginHookContext {\n scenario?: VisualScenario;\n targetMode: 'desktop' | 'preview' | string;\n artifactsDir: string;\n cliOptions?: Record<string, unknown>;\n state: Map<string, unknown>;\n}\n\nexport interface AgentLensPlugin {\n /** Unique plugin identifier (e.g. 'desktop-webview2', 'live-controller', 'mock-ipc') */\n name: string;\n version?: string;\n\n /** Lifecycle hook: initial setup before drivers and scenarios start */\n setup?: (context: PluginHookContext) => Promise<void> | void;\n\n /**\n * Microkernel driver hook: allows a plugin to provide a custom browser/page session\n * (e.g., desktop WebView2 CDP connection, custom remote browser).\n */\n launchSession?: (\n options: { currentViewport: { width: number; height: number }; headed?: boolean },\n hookContext: PluginHookContext\n ) => Promise<DriverLaunchResult | undefined>;\n\n /** Lifecycle hook: called when browser context is ready */\n onContextCreated?: (context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: called when the test page is created/navigated */\n onPageCreated?: (page: Page, context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;\n\n /**\n * Context extension: inject custom methods or properties directly into TestContext (ctx)\n */\n extendContext?: (\n ctx: TestContext,\n page: Page,\n hookContext: PluginHookContext\n ) => Record<string, any> | Promise<Record<string, any>>;\n\n /** Lifecycle hook: called after scenario runs and report data is calculated */\n onAfterRun?: (reportData: ReportData, hookContext: PluginHookContext) => Promise<void> | void;\n\n /** Lifecycle hook: teardown and cleanup guaranteed to run */\n teardown?: (hookContext: PluginHookContext) => Promise<void> | void;\n}\n\nexport function definePlugin(plugin: AgentLensPlugin): AgentLensPlugin {\n return plugin;\n}","export interface AccessibilityNode {\n role: string;\n name?: string;\n value?: string | number;\n description?: string;\n keyshortcuts?: string;\n roledescription?: string;\n valuetext?: string;\n disabled?: boolean;\n expanded?: boolean;\n focused?: boolean;\n modal?: boolean;\n multiline?: boolean;\n multiselectable?: boolean;\n readonly?: boolean;\n required?: boolean;\n selected?: boolean;\n checked?: boolean | 'mixed';\n pressed?: boolean | 'mixed';\n level?: number;\n valuemin?: number;\n valuemax?: number;\n autocomplete?: string;\n haspopup?: string;\n invalid?: string;\n orientation?: string;\n children?: AccessibilityNode[];\n}\n\n/**\n * Format an accessibility tree node into clean, indented Markdown for text-based LLMs.\n */\nexport function formatAccessibilityTree(\n node: AccessibilityNode | null,\n depth = 0,\n options?: { compact?: boolean }\n): string {\n if (!node) return '';\n\n const indent = ' '.repeat(depth);\n const parts: string[] = [`${indent}- role: **${node.role}**`];\n\n if (node.name) {\n parts.push(`\"${node.name.replace(/\"/g, '\\\\\"')}\"`);\n }\n\n const flags: string[] = [];\n if (node.level !== undefined) flags.push(`level ${node.level}`);\n if (node.disabled) flags.push('disabled');\n if (node.required) flags.push('required');\n if (node.focused) flags.push('focused');\n if (node.checked !== undefined && node.checked !== false) flags.push(`checked: ${String(node.checked)}`);\n if (node.pressed !== undefined) flags.push(`pressed: ${String(node.pressed)}`);\n if (node.selected) flags.push('selected');\n if (node.value !== undefined && node.value !== '') flags.push(`value: \"${String(node.value)}\"`);\n\n if (flags.length > 0) {\n parts.push(`[${flags.join(', ')}]`);\n }\n\n let result = parts.join(' ') + '\\n';\n\n if (node.children && node.children.length > 0) {\n for (const child of node.children) {\n if (options?.compact && !child.name && (!child.children || child.children.length === 0)) {\n continue;\n }\n result += formatAccessibilityTree(child, depth + 1, options);\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":["path","fs"]}
|