@_deep4wee/agent-lens 1.1.0 ā 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 +331 -227
- package/dist/cli.js +1068 -911
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1277 -1033
- 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 -178
- package/dist/index.d.ts +131 -178
- 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 +130 -43
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/live-controller/index.ts","../../../src/shared/api/plugin.ts","../../../src/plugins/live-controller/actions.ts","../../../src/plugins/live-controller/session.ts","../../../src/shared/lib/playwrightLoader.ts"],"sourcesContent":["import { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport {\n clickCoords,\n dragAndDrop,\n scrollPercent,\n snapLive,\n handleLiveCli,\n type SnapLiveOptions\n} from './actions';\n\nexport {\n clickCoords,\n dragAndDrop,\n scrollPercent,\n snapLive,\n handleLiveCli,\n type SnapLiveOptions\n};\n\nexport const liveControllerPlugin: AgentLensPlugin = definePlugin({\n name: 'live-controller',\n version: '1.0.0',\n\n extendContext: (_ctx, page) => {\n return {\n clickCoords: async (\n x: number,\n y: number,\n options?: { button?: 'left' | 'right' | 'middle'; clickCount?: number }\n ) => {\n await clickCoords(page, x, y, options);\n },\n\n dragAndDrop: async (\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n steps?: number\n ) => {\n await dragAndDrop(page, fromX, fromY, toX, toY, steps);\n },\n\n scrollPercent: async (percent: number) => {\n await scrollPercent(page, percent);\n },\n\n snapLive: async (options?: SnapLiveOptions) => {\n return await snapLive(page, options);\n }\n };\n }\n});\n\nexport default liveControllerPlugin;\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}","import path from 'path';\nimport type { Page } from 'playwright';\nimport {\n connectToLiveSession,\n saveLiveSession,\n stopLiveSession,\n getLiveArtifactsDir\n} from './session';\nimport { chromium } from '../../shared/lib/playwrightLoader';\n\nexport interface SnapLiveOptions {\n name?: string;\n fullPage?: boolean;\n selector?: string;\n}\n\n/**\n * Click at physical pixel coordinates on the page (for Vision AI models).\n */\nexport async function clickCoords(\n page: Page,\n x: number,\n y: number,\n options?: { button?: 'left' | 'right' | 'middle'; clickCount?: number }\n): Promise<void> {\n console.log(`š±ļø [LiveController] Clicking coordinates: (${x}, ${y})`);\n await page.mouse.click(x, y, options);\n await page.waitForTimeout(200);\n}\n\n/**\n * Drag and drop from (fromX, fromY) to (toX, toY).\n */\nexport async function dragAndDrop(\n page: Page,\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n steps = 10\n): Promise<void> {\n console.log(`šļø [LiveController] Dragging from (${fromX}, ${fromY}) to (${toX}, ${toY}) in ${steps} steps`);\n await page.mouse.move(fromX, fromY);\n await page.mouse.down();\n await page.mouse.move(toX, toY, { steps });\n await page.mouse.up();\n await page.waitForTimeout(200);\n}\n\n/**\n * Scroll page by percentage (0 to 100).\n */\nexport async function scrollPercent(page: Page, percent: number): Promise<void> {\n const clamped = Math.max(0, Math.min(100, percent));\n console.log(`š [LiveController] Scrolling to ${clamped}% of page height`);\n await page.evaluate((pct) => {\n const totalHeight = document.documentElement.scrollHeight - window.innerHeight;\n const targetY = (totalHeight * pct) / 100;\n window.scrollTo({ top: targetY, behavior: 'smooth' });\n }, clamped);\n await page.waitForTimeout(300);\n}\n\n/**\n * Capture a live snapshot with optional fullPage (--full) support.\n */\nexport async function snapLive(page: Page, options?: SnapLiveOptions): Promise<string> {\n const artifactsDir = getLiveArtifactsDir();\n const fileName = options?.name ? `${options.name}.png` : 'current.png';\n const filePath = path.join(artifactsDir, fileName);\n\n if (options?.selector) {\n const el = await page.waitForSelector(options.selector, { timeout: 5000 });\n await el.screenshot({ path: filePath });\n } else {\n await page.screenshot({ path: filePath, fullPage: options?.fullPage ?? false });\n }\n\n console.log(`šø [LiveController] Snapshot saved: ${filePath}${options?.fullPage ? ' (Full Page)' : ''}`);\n return filePath;\n}\n\n/**\n * Handle CLI commands: live start, click, type, snap, stop\n */\nexport async function handleLiveCli(argv: string[]): Promise<boolean> {\n const action = argv[0]?.toLowerCase();\n\n switch (action) {\n case 'start': {\n const urlArg = argv.find((a) => a.startsWith('--url='))?.split('=')[1] || 'http://localhost:5173';\n const portArg = parseInt(argv.find((a) => a.startsWith('--port='))?.split('=')[1] || '9223', 10);\n const isHeaded = argv.includes('--headed');\n\n console.log(`š [Live] Starting background browser for ${urlArg} on CDP port ${portArg}...`);\n\n const browser = await chromium.launch({\n headless: !isHeaded,\n args: [`--remote-debugging-port=${portArg}`, '--no-sandbox']\n });\n\n const context = await browser.newContext({ viewport: { width: 1200, height: 800 } });\n const page = await context.newPage();\n await page.goto(urlArg, { waitUntil: 'domcontentloaded' });\n await page.waitForTimeout(500);\n\n saveLiveSession({\n port: portArg,\n url: urlArg,\n startedAt: new Date().toISOString()\n });\n\n await snapLive(page, { name: 'current' });\n console.log(`ā
[Live] Session active! You can now send live commands:\\n`);\n console.log(` npx agent-lens live click 450 120`);\n console.log(` npx agent-lens live type \"input\" \"hello\"`);\n console.log(` npx agent-lens live snap --full`);\n console.log(` npx agent-lens live stop\\n`);\n return true;\n }\n\n case 'click': {\n const { page, browser } = await connectToLiveSession();\n const firstArg = argv[1];\n const secondArg = argv[2];\n\n const x = parseInt(firstArg, 10);\n const y = parseInt(secondArg, 10);\n\n if (!isNaN(x) && !isNaN(y)) {\n await clickCoords(page, x, y);\n } else if (firstArg) {\n console.log(`š±ļø [Live] Clicking selector: \"${firstArg}\"`);\n await page.click(firstArg);\n } else {\n console.error(`ā Usage: npx agent-lens live click <x> <y> OR npx agent-lens live click <selector>`);\n await browser.close();\n return false;\n }\n\n await snapLive(page, { name: 'current' });\n return true;\n }\n\n case 'type': {\n const { page, browser } = await connectToLiveSession();\n const selector = argv[1];\n const text = argv[2];\n\n if (!selector || text === undefined) {\n console.error(`ā Usage: npx agent-lens live type <selector> <text>`);\n await browser.close();\n return false;\n }\n\n console.log(`āØļø [Live] Typing into \"${selector}\": \"${text}\"`);\n await page.fill(selector, text);\n await snapLive(page, { name: 'current' });\n return true;\n }\n\n case 'snap': {\n const { page } = await connectToLiveSession();\n const isFull = argv.includes('--full');\n const nameArg = argv.find((a) => !a.startsWith('--') && a !== 'snap');\n\n await snapLive(page, { name: nameArg || 'current', fullPage: isFull });\n return true;\n }\n\n case 'stop': {\n return await stopLiveSession();\n }\n\n default: {\n console.log(`\nā¹ļø AgentLens Live Controller:\n npx agent-lens live start --url=<url> Start background live session\n npx agent-lens live click <x> <y> Click at pixel coordinates\n npx agent-lens live click <selector> Click CSS selector\n npx agent-lens live type <sel> <text> Fill input field\n npx agent-lens live snap [name] [--full] Capture current or full-page screenshot\n npx agent-lens live stop Close live browser and finish\n `);\n return true;\n }\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { Page, Browser, BrowserContext } from 'playwright';\nimport { chromium } from '../../shared/lib/playwrightLoader';\nimport treeKill from 'tree-kill';\n\nexport interface LiveSessionInfo {\n port: number;\n url: string;\n pid?: number;\n startedAt: string;\n}\n\nconst SESSION_DIR = path.resolve(process.cwd(), '.agent-lens');\nconst SESSION_FILE = path.join(SESSION_DIR, 'live-session.json');\nconst LIVE_ARTIFACTS_DIR = path.resolve(process.cwd(), 'artifacts', 'live');\n\nexport function getSessionFile(): string {\n return SESSION_FILE;\n}\n\nexport function getLiveArtifactsDir(): string {\n if (!fs.existsSync(LIVE_ARTIFACTS_DIR)) {\n fs.mkdirSync(LIVE_ARTIFACTS_DIR, { recursive: true });\n }\n return LIVE_ARTIFACTS_DIR;\n}\n\nexport function readLiveSession(): LiveSessionInfo | null {\n if (!fs.existsSync(SESSION_FILE)) return null;\n try {\n return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nexport function saveLiveSession(info: LiveSessionInfo): void {\n if (!fs.existsSync(SESSION_DIR)) {\n fs.mkdirSync(SESSION_DIR, { recursive: true });\n }\n fs.writeFileSync(SESSION_FILE, JSON.stringify(info, null, 2), 'utf-8');\n}\n\nexport function clearLiveSession(): void {\n if (fs.existsSync(SESSION_FILE)) {\n try {\n fs.unlinkSync(SESSION_FILE);\n } catch {\n // ignore\n }\n }\n}\n\n/**\n * Connect to an already running Live Browser session over CDP.\n */\nexport async function connectToLiveSession(): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {\n const session = readLiveSession();\n if (!session) {\n throw new Error(\n `No active live session found. Start one with: npx agent-lens live start --url=http://localhost:5173`\n );\n }\n\n const browser = await chromium.connectOverCDP(`http://127.0.0.1:${session.port}`);\n const contexts = browser.contexts();\n const context = contexts[0] || (await browser.newContext());\n const pages = context.pages();\n const page = pages[0] || (await context.waitForEvent('page', { timeout: 5000 }));\n\n return { browser, context, page };\n}\n\n/**\n * Stop running live session and clean up process and session file.\n */\nexport async function stopLiveSession(): Promise<boolean> {\n const session = readLiveSession();\n if (!session) {\n console.log(`ā¹ļø [Live] No active session to stop.`);\n return false;\n }\n\n try {\n const { browser } = await connectToLiveSession();\n await browser.close().catch(() => {});\n } catch {\n // browser might already be closed\n }\n\n if (session.pid) {\n await new Promise<void>((resolve) => {\n treeKill(session.pid!, 'SIGTERM', () => resolve());\n });\n }\n\n clearLiveSession();\n console.log(`š [Live] Session stopped and cleaned up.`);\n return true;\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});"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC0DO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;;;AC5DA,IAAAA,eAAiB;;;ACAjB,gBAAe;AACf,kBAAiB;;;ACDjB,wBAAiF;AAK1E,IAAM,WAAyC,IAAI,MAAM,kBAAAC,UAAc;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;;;ADvBD,uBAAqB;AASrB,IAAM,cAAc,YAAAC,QAAK,QAAQ,QAAQ,IAAI,GAAG,aAAa;AAC7D,IAAM,eAAe,YAAAA,QAAK,KAAK,aAAa,mBAAmB;AAC/D,IAAM,qBAAqB,YAAAA,QAAK,QAAQ,QAAQ,IAAI,GAAG,aAAa,MAAM;AAMnE,SAAS,sBAA8B;AAC5C,MAAI,CAAC,UAAAC,QAAG,WAAW,kBAAkB,GAAG;AACtC,cAAAA,QAAG,UAAU,oBAAoB,EAAE,WAAW,KAAK,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAEO,SAAS,kBAA0C;AACxD,MAAI,CAAC,UAAAA,QAAG,WAAW,YAAY,EAAG,QAAO;AACzC,MAAI;AACF,WAAO,KAAK,MAAM,UAAAA,QAAG,aAAa,cAAc,OAAO,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,MAA6B;AAC3D,MAAI,CAAC,UAAAA,QAAG,WAAW,WAAW,GAAG;AAC/B,cAAAA,QAAG,UAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AACA,YAAAA,QAAG,cAAc,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AACvE;AAEO,SAAS,mBAAyB;AACvC,MAAI,UAAAA,QAAG,WAAW,YAAY,GAAG;AAC/B,QAAI;AACF,gBAAAA,QAAG,WAAW,YAAY;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKA,eAAsB,uBAA2F;AAC/G,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,SAAS,eAAe,oBAAoB,QAAQ,IAAI,EAAE;AAChF,QAAM,WAAW,QAAQ,SAAS;AAClC,QAAM,UAAU,SAAS,CAAC,KAAM,MAAM,QAAQ,WAAW;AACzD,QAAM,QAAQ,QAAQ,MAAM;AAC5B,QAAM,OAAO,MAAM,CAAC,KAAM,MAAM,QAAQ,aAAa,QAAQ,EAAE,SAAS,IAAK,CAAC;AAE9E,SAAO,EAAE,SAAS,SAAS,KAAK;AAClC;AAKA,eAAsB,kBAAoC;AACxD,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,gDAAsC;AAClD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,qBAAqB;AAC/C,UAAM,QAAQ,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACtC,QAAQ;AAAA,EAER;AAEA,MAAI,QAAQ,KAAK;AACf,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,2BAAAC,SAAS,QAAQ,KAAM,WAAW,MAAM,QAAQ,CAAC;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,mBAAiB;AACjB,UAAQ,IAAI,kDAA2C;AACvD,SAAO;AACT;;;ADjFA,eAAsB,YACpB,MACA,GACA,GACA,SACe;AACf,UAAQ,IAAI,2DAA+C,CAAC,KAAK,CAAC,GAAG;AACrE,QAAM,KAAK,MAAM,MAAM,GAAG,GAAG,OAAO;AACpC,QAAM,KAAK,eAAe,GAAG;AAC/B;AAKA,eAAsB,YACpB,MACA,OACA,OACA,KACA,KACA,QAAQ,IACO;AACf,UAAQ,IAAI,mDAAuC,KAAK,KAAK,KAAK,SAAS,GAAG,KAAK,GAAG,QAAQ,KAAK,QAAQ;AAC3G,QAAM,KAAK,MAAM,KAAK,OAAO,KAAK;AAClC,QAAM,KAAK,MAAM,KAAK;AACtB,QAAM,KAAK,MAAM,KAAK,KAAK,KAAK,EAAE,MAAM,CAAC;AACzC,QAAM,KAAK,MAAM,GAAG;AACpB,QAAM,KAAK,eAAe,GAAG;AAC/B;AAKA,eAAsB,cAAc,MAAY,SAAgC;AAC9E,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,CAAC;AAClD,UAAQ,IAAI,2CAAoC,OAAO,kBAAkB;AACzE,QAAM,KAAK,SAAS,CAAC,QAAQ;AAC3B,UAAM,cAAc,SAAS,gBAAgB,eAAe,OAAO;AACnE,UAAM,UAAW,cAAc,MAAO;AACtC,WAAO,SAAS,EAAE,KAAK,SAAS,UAAU,SAAS,CAAC;AAAA,EACtD,GAAG,OAAO;AACV,QAAM,KAAK,eAAe,GAAG;AAC/B;AAKA,eAAsB,SAAS,MAAY,SAA4C;AACrF,QAAM,eAAe,oBAAoB;AACzC,QAAM,WAAW,SAAS,OAAO,GAAG,QAAQ,IAAI,SAAS;AACzD,QAAM,WAAW,aAAAC,QAAK,KAAK,cAAc,QAAQ;AAEjD,MAAI,SAAS,UAAU;AACrB,UAAM,KAAK,MAAM,KAAK,gBAAgB,QAAQ,UAAU,EAAE,SAAS,IAAK,CAAC;AACzE,UAAM,GAAG,WAAW,EAAE,MAAM,SAAS,CAAC;AAAA,EACxC,OAAO;AACL,UAAM,KAAK,WAAW,EAAE,MAAM,UAAU,UAAU,SAAS,YAAY,MAAM,CAAC;AAAA,EAChF;AAEA,UAAQ,IAAI,8CAAuC,QAAQ,GAAG,SAAS,WAAW,iBAAiB,EAAE,EAAE;AACvG,SAAO;AACT;AAKA,eAAsB,cAAc,MAAkC;AACpE,QAAM,SAAS,KAAK,CAAC,GAAG,YAAY;AAEpC,UAAQ,QAAQ;AAAA,IACd,KAAK,SAAS;AACZ,YAAM,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,KAAK;AAC1E,YAAM,UAAU,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE;AAC/F,YAAM,WAAW,KAAK,SAAS,UAAU;AAEzC,cAAQ,IAAI,oDAA6C,MAAM,gBAAgB,OAAO,KAAK;AAE3F,YAAM,UAAU,MAAM,SAAS,OAAO;AAAA,QACpC,UAAU,CAAC;AAAA,QACX,MAAM,CAAC,2BAA2B,OAAO,IAAI,cAAc;AAAA,MAC7D,CAAC;AAED,YAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,UAAU,EAAE,OAAO,MAAM,QAAQ,IAAI,EAAE,CAAC;AACnF,YAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,YAAM,KAAK,KAAK,QAAQ,EAAE,WAAW,mBAAmB,CAAC;AACzD,YAAM,KAAK,eAAe,GAAG;AAE7B,sBAAgB;AAAA,QACd,MAAM;AAAA,QACN,KAAK;AAAA,QACL,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AAED,YAAM,SAAS,MAAM,EAAE,MAAM,UAAU,CAAC;AACxC,cAAQ,IAAI;AAAA,CAA4D;AACxE,cAAQ,IAAI,sCAAsC;AAClD,cAAQ,IAAI,6CAA6C;AACzD,cAAQ,IAAI,oCAAoC;AAChD,cAAQ,IAAI;AAAA,CAA+B;AAC3C,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,SAAS;AACZ,YAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,qBAAqB;AACrD,YAAM,WAAW,KAAK,CAAC;AACvB,YAAM,YAAY,KAAK,CAAC;AAExB,YAAM,IAAI,SAAS,UAAU,EAAE;AAC/B,YAAM,IAAI,SAAS,WAAW,EAAE;AAEhC,UAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;AAC1B,cAAM,YAAY,MAAM,GAAG,CAAC;AAAA,MAC9B,WAAW,UAAU;AACnB,gBAAQ,IAAI,8CAAkC,QAAQ,GAAG;AACzD,cAAM,KAAK,MAAM,QAAQ;AAAA,MAC3B,OAAO;AACL,gBAAQ,MAAM,yFAAoF;AAClG,cAAM,QAAQ,MAAM;AACpB,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,MAAM,EAAE,MAAM,UAAU,CAAC;AACxC,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,QAAQ;AACX,YAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,qBAAqB;AACrD,YAAM,WAAW,KAAK,CAAC;AACvB,YAAM,OAAO,KAAK,CAAC;AAEnB,UAAI,CAAC,YAAY,SAAS,QAAW;AACnC,gBAAQ,MAAM,0DAAqD;AACnE,cAAM,QAAQ,MAAM;AACpB,eAAO;AAAA,MACT;AAEA,cAAQ,IAAI,oCAA0B,QAAQ,OAAO,IAAI,GAAG;AAC5D,YAAM,KAAK,KAAK,UAAU,IAAI;AAC9B,YAAM,SAAS,MAAM,EAAE,MAAM,UAAU,CAAC;AACxC,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,QAAQ;AACX,YAAM,EAAE,KAAK,IAAI,MAAM,qBAAqB;AAC5C,YAAM,SAAS,KAAK,SAAS,QAAQ;AACrC,YAAM,UAAU,KAAK,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,IAAI,KAAK,MAAM,MAAM;AAEpE,YAAM,SAAS,MAAM,EAAE,MAAM,WAAW,WAAW,UAAU,OAAO,CAAC;AACrE,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,QAAQ;AACX,aAAO,MAAM,gBAAgB;AAAA,IAC/B;AAAA,IAEA,SAAS;AACP,cAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQX;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AFxKO,IAAM,uBAAwC,aAAa;AAAA,EAChE,MAAM;AAAA,EACN,SAAS;AAAA,EAET,eAAe,CAAC,MAAM,SAAS;AAC7B,WAAO;AAAA,MACL,aAAa,OACX,GACA,GACA,YACG;AACH,cAAM,YAAY,MAAM,GAAG,GAAG,OAAO;AAAA,MACvC;AAAA,MAEA,aAAa,OACX,OACA,OACA,KACA,KACA,UACG;AACH,cAAM,YAAY,MAAM,OAAO,OAAO,KAAK,KAAK,KAAK;AAAA,MACvD;AAAA,MAEA,eAAe,OAAO,YAAoB;AACxC,cAAM,cAAc,MAAM,OAAO;AAAA,MACnC;AAAA,MAEA,UAAU,OAAO,YAA8B;AAC7C,eAAO,MAAM,SAAS,MAAM,OAAO;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,IAAO,0BAAQ;","names":["import_path","baseChromium","path","fs","treeKill","path"]}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/shared/api/plugin.ts
|
|
2
|
+
function definePlugin(plugin) {
|
|
3
|
+
return plugin;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/plugins/live-controller/actions.ts
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
|
|
9
|
+
// src/plugins/live-controller/session.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
|
|
13
|
+
// src/shared/lib/playwrightLoader.ts
|
|
14
|
+
import { chromium as baseChromium } from "playwright";
|
|
15
|
+
var chromium = new Proxy(baseChromium, {
|
|
16
|
+
get(target, prop, receiver) {
|
|
17
|
+
if (prop === "launch") {
|
|
18
|
+
return async (...args) => {
|
|
19
|
+
try {
|
|
20
|
+
return await target.launch(...args);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err.message?.includes("Executable doesn't exist") || err.message?.includes("playwright install") || err.message?.includes("browser has not been downloaded")) {
|
|
23
|
+
console.error("\n\u274C [AgentLens] Playwright Chromium browser binary is missing!");
|
|
24
|
+
console.error("\u{1F449} Please install it by running: npx playwright install chromium\n");
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const val = Reflect.get(target, prop, receiver);
|
|
31
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// src/plugins/live-controller/session.ts
|
|
36
|
+
import treeKill from "tree-kill";
|
|
37
|
+
var SESSION_DIR = path.resolve(process.cwd(), ".agent-lens");
|
|
38
|
+
var SESSION_FILE = path.join(SESSION_DIR, "live-session.json");
|
|
39
|
+
var LIVE_ARTIFACTS_DIR = path.resolve(process.cwd(), "artifacts", "live");
|
|
40
|
+
function getLiveArtifactsDir() {
|
|
41
|
+
if (!fs.existsSync(LIVE_ARTIFACTS_DIR)) {
|
|
42
|
+
fs.mkdirSync(LIVE_ARTIFACTS_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
return LIVE_ARTIFACTS_DIR;
|
|
45
|
+
}
|
|
46
|
+
function readLiveSession() {
|
|
47
|
+
if (!fs.existsSync(SESSION_FILE)) return null;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(fs.readFileSync(SESSION_FILE, "utf-8"));
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function saveLiveSession(info) {
|
|
55
|
+
if (!fs.existsSync(SESSION_DIR)) {
|
|
56
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(info, null, 2), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
function clearLiveSession() {
|
|
61
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(SESSION_FILE);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function connectToLiveSession() {
|
|
69
|
+
const session = readLiveSession();
|
|
70
|
+
if (!session) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`No active live session found. Start one with: npx agent-lens live start --url=http://localhost:5173`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${session.port}`);
|
|
76
|
+
const contexts = browser.contexts();
|
|
77
|
+
const context = contexts[0] || await browser.newContext();
|
|
78
|
+
const pages = context.pages();
|
|
79
|
+
const page = pages[0] || await context.waitForEvent("page", { timeout: 5e3 });
|
|
80
|
+
return { browser, context, page };
|
|
81
|
+
}
|
|
82
|
+
async function stopLiveSession() {
|
|
83
|
+
const session = readLiveSession();
|
|
84
|
+
if (!session) {
|
|
85
|
+
console.log(`\u2139\uFE0F [Live] No active session to stop.`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const { browser } = await connectToLiveSession();
|
|
90
|
+
await browser.close().catch(() => {
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
if (session.pid) {
|
|
95
|
+
await new Promise((resolve) => {
|
|
96
|
+
treeKill(session.pid, "SIGTERM", () => resolve());
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
clearLiveSession();
|
|
100
|
+
console.log(`\u{1F6D1} [Live] Session stopped and cleaned up.`);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/plugins/live-controller/actions.ts
|
|
105
|
+
async function clickCoords(page, x, y, options) {
|
|
106
|
+
console.log(`\u{1F5B1}\uFE0F [LiveController] Clicking coordinates: (${x}, ${y})`);
|
|
107
|
+
await page.mouse.click(x, y, options);
|
|
108
|
+
await page.waitForTimeout(200);
|
|
109
|
+
}
|
|
110
|
+
async function dragAndDrop(page, fromX, fromY, toX, toY, steps = 10) {
|
|
111
|
+
console.log(`\u{1F590}\uFE0F [LiveController] Dragging from (${fromX}, ${fromY}) to (${toX}, ${toY}) in ${steps} steps`);
|
|
112
|
+
await page.mouse.move(fromX, fromY);
|
|
113
|
+
await page.mouse.down();
|
|
114
|
+
await page.mouse.move(toX, toY, { steps });
|
|
115
|
+
await page.mouse.up();
|
|
116
|
+
await page.waitForTimeout(200);
|
|
117
|
+
}
|
|
118
|
+
async function scrollPercent(page, percent) {
|
|
119
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
120
|
+
console.log(`\u{1F4DC} [LiveController] Scrolling to ${clamped}% of page height`);
|
|
121
|
+
await page.evaluate((pct) => {
|
|
122
|
+
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
123
|
+
const targetY = totalHeight * pct / 100;
|
|
124
|
+
window.scrollTo({ top: targetY, behavior: "smooth" });
|
|
125
|
+
}, clamped);
|
|
126
|
+
await page.waitForTimeout(300);
|
|
127
|
+
}
|
|
128
|
+
async function snapLive(page, options) {
|
|
129
|
+
const artifactsDir = getLiveArtifactsDir();
|
|
130
|
+
const fileName = options?.name ? `${options.name}.png` : "current.png";
|
|
131
|
+
const filePath = path2.join(artifactsDir, fileName);
|
|
132
|
+
if (options?.selector) {
|
|
133
|
+
const el = await page.waitForSelector(options.selector, { timeout: 5e3 });
|
|
134
|
+
await el.screenshot({ path: filePath });
|
|
135
|
+
} else {
|
|
136
|
+
await page.screenshot({ path: filePath, fullPage: options?.fullPage ?? false });
|
|
137
|
+
}
|
|
138
|
+
console.log(`\u{1F4F8} [LiveController] Snapshot saved: ${filePath}${options?.fullPage ? " (Full Page)" : ""}`);
|
|
139
|
+
return filePath;
|
|
140
|
+
}
|
|
141
|
+
async function handleLiveCli(argv) {
|
|
142
|
+
const action = argv[0]?.toLowerCase();
|
|
143
|
+
switch (action) {
|
|
144
|
+
case "start": {
|
|
145
|
+
const urlArg = argv.find((a) => a.startsWith("--url="))?.split("=")[1] || "http://localhost:5173";
|
|
146
|
+
const portArg = parseInt(argv.find((a) => a.startsWith("--port="))?.split("=")[1] || "9223", 10);
|
|
147
|
+
const isHeaded = argv.includes("--headed");
|
|
148
|
+
console.log(`\u{1F680} [Live] Starting background browser for ${urlArg} on CDP port ${portArg}...`);
|
|
149
|
+
const browser = await chromium.launch({
|
|
150
|
+
headless: !isHeaded,
|
|
151
|
+
args: [`--remote-debugging-port=${portArg}`, "--no-sandbox"]
|
|
152
|
+
});
|
|
153
|
+
const context = await browser.newContext({ viewport: { width: 1200, height: 800 } });
|
|
154
|
+
const page = await context.newPage();
|
|
155
|
+
await page.goto(urlArg, { waitUntil: "domcontentloaded" });
|
|
156
|
+
await page.waitForTimeout(500);
|
|
157
|
+
saveLiveSession({
|
|
158
|
+
port: portArg,
|
|
159
|
+
url: urlArg,
|
|
160
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
161
|
+
});
|
|
162
|
+
await snapLive(page, { name: "current" });
|
|
163
|
+
console.log(`\u2705 [Live] Session active! You can now send live commands:
|
|
164
|
+
`);
|
|
165
|
+
console.log(` npx agent-lens live click 450 120`);
|
|
166
|
+
console.log(` npx agent-lens live type "input" "hello"`);
|
|
167
|
+
console.log(` npx agent-lens live snap --full`);
|
|
168
|
+
console.log(` npx agent-lens live stop
|
|
169
|
+
`);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
case "click": {
|
|
173
|
+
const { page, browser } = await connectToLiveSession();
|
|
174
|
+
const firstArg = argv[1];
|
|
175
|
+
const secondArg = argv[2];
|
|
176
|
+
const x = parseInt(firstArg, 10);
|
|
177
|
+
const y = parseInt(secondArg, 10);
|
|
178
|
+
if (!isNaN(x) && !isNaN(y)) {
|
|
179
|
+
await clickCoords(page, x, y);
|
|
180
|
+
} else if (firstArg) {
|
|
181
|
+
console.log(`\u{1F5B1}\uFE0F [Live] Clicking selector: "${firstArg}"`);
|
|
182
|
+
await page.click(firstArg);
|
|
183
|
+
} else {
|
|
184
|
+
console.error(`\u274C Usage: npx agent-lens live click <x> <y> OR npx agent-lens live click <selector>`);
|
|
185
|
+
await browser.close();
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
await snapLive(page, { name: "current" });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
case "type": {
|
|
192
|
+
const { page, browser } = await connectToLiveSession();
|
|
193
|
+
const selector = argv[1];
|
|
194
|
+
const text = argv[2];
|
|
195
|
+
if (!selector || text === void 0) {
|
|
196
|
+
console.error(`\u274C Usage: npx agent-lens live type <selector> <text>`);
|
|
197
|
+
await browser.close();
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
console.log(`\u2328\uFE0F [Live] Typing into "${selector}": "${text}"`);
|
|
201
|
+
await page.fill(selector, text);
|
|
202
|
+
await snapLive(page, { name: "current" });
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
case "snap": {
|
|
206
|
+
const { page } = await connectToLiveSession();
|
|
207
|
+
const isFull = argv.includes("--full");
|
|
208
|
+
const nameArg = argv.find((a) => !a.startsWith("--") && a !== "snap");
|
|
209
|
+
await snapLive(page, { name: nameArg || "current", fullPage: isFull });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
case "stop": {
|
|
213
|
+
return await stopLiveSession();
|
|
214
|
+
}
|
|
215
|
+
default: {
|
|
216
|
+
console.log(`
|
|
217
|
+
\u2139\uFE0F AgentLens Live Controller:
|
|
218
|
+
npx agent-lens live start --url=<url> Start background live session
|
|
219
|
+
npx agent-lens live click <x> <y> Click at pixel coordinates
|
|
220
|
+
npx agent-lens live click <selector> Click CSS selector
|
|
221
|
+
npx agent-lens live type <sel> <text> Fill input field
|
|
222
|
+
npx agent-lens live snap [name] [--full] Capture current or full-page screenshot
|
|
223
|
+
npx agent-lens live stop Close live browser and finish
|
|
224
|
+
`);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/plugins/live-controller/index.ts
|
|
231
|
+
var liveControllerPlugin = definePlugin({
|
|
232
|
+
name: "live-controller",
|
|
233
|
+
version: "1.0.0",
|
|
234
|
+
extendContext: (_ctx, page) => {
|
|
235
|
+
return {
|
|
236
|
+
clickCoords: async (x, y, options) => {
|
|
237
|
+
await clickCoords(page, x, y, options);
|
|
238
|
+
},
|
|
239
|
+
dragAndDrop: async (fromX, fromY, toX, toY, steps) => {
|
|
240
|
+
await dragAndDrop(page, fromX, fromY, toX, toY, steps);
|
|
241
|
+
},
|
|
242
|
+
scrollPercent: async (percent) => {
|
|
243
|
+
await scrollPercent(page, percent);
|
|
244
|
+
},
|
|
245
|
+
snapLive: async (options) => {
|
|
246
|
+
return await snapLive(page, options);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
var live_controller_default = liveControllerPlugin;
|
|
252
|
+
export {
|
|
253
|
+
clickCoords,
|
|
254
|
+
live_controller_default as default,
|
|
255
|
+
dragAndDrop,
|
|
256
|
+
handleLiveCli,
|
|
257
|
+
liveControllerPlugin,
|
|
258
|
+
scrollPercent,
|
|
259
|
+
snapLive
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/shared/api/plugin.ts","../../../src/plugins/live-controller/actions.ts","../../../src/plugins/live-controller/session.ts","../../../src/shared/lib/playwrightLoader.ts","../../../src/plugins/live-controller/index.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 path from 'path';\nimport type { Page } from 'playwright';\nimport {\n connectToLiveSession,\n saveLiveSession,\n stopLiveSession,\n getLiveArtifactsDir\n} from './session';\nimport { chromium } from '../../shared/lib/playwrightLoader';\n\nexport interface SnapLiveOptions {\n name?: string;\n fullPage?: boolean;\n selector?: string;\n}\n\n/**\n * Click at physical pixel coordinates on the page (for Vision AI models).\n */\nexport async function clickCoords(\n page: Page,\n x: number,\n y: number,\n options?: { button?: 'left' | 'right' | 'middle'; clickCount?: number }\n): Promise<void> {\n console.log(`š±ļø [LiveController] Clicking coordinates: (${x}, ${y})`);\n await page.mouse.click(x, y, options);\n await page.waitForTimeout(200);\n}\n\n/**\n * Drag and drop from (fromX, fromY) to (toX, toY).\n */\nexport async function dragAndDrop(\n page: Page,\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n steps = 10\n): Promise<void> {\n console.log(`šļø [LiveController] Dragging from (${fromX}, ${fromY}) to (${toX}, ${toY}) in ${steps} steps`);\n await page.mouse.move(fromX, fromY);\n await page.mouse.down();\n await page.mouse.move(toX, toY, { steps });\n await page.mouse.up();\n await page.waitForTimeout(200);\n}\n\n/**\n * Scroll page by percentage (0 to 100).\n */\nexport async function scrollPercent(page: Page, percent: number): Promise<void> {\n const clamped = Math.max(0, Math.min(100, percent));\n console.log(`š [LiveController] Scrolling to ${clamped}% of page height`);\n await page.evaluate((pct) => {\n const totalHeight = document.documentElement.scrollHeight - window.innerHeight;\n const targetY = (totalHeight * pct) / 100;\n window.scrollTo({ top: targetY, behavior: 'smooth' });\n }, clamped);\n await page.waitForTimeout(300);\n}\n\n/**\n * Capture a live snapshot with optional fullPage (--full) support.\n */\nexport async function snapLive(page: Page, options?: SnapLiveOptions): Promise<string> {\n const artifactsDir = getLiveArtifactsDir();\n const fileName = options?.name ? `${options.name}.png` : 'current.png';\n const filePath = path.join(artifactsDir, fileName);\n\n if (options?.selector) {\n const el = await page.waitForSelector(options.selector, { timeout: 5000 });\n await el.screenshot({ path: filePath });\n } else {\n await page.screenshot({ path: filePath, fullPage: options?.fullPage ?? false });\n }\n\n console.log(`šø [LiveController] Snapshot saved: ${filePath}${options?.fullPage ? ' (Full Page)' : ''}`);\n return filePath;\n}\n\n/**\n * Handle CLI commands: live start, click, type, snap, stop\n */\nexport async function handleLiveCli(argv: string[]): Promise<boolean> {\n const action = argv[0]?.toLowerCase();\n\n switch (action) {\n case 'start': {\n const urlArg = argv.find((a) => a.startsWith('--url='))?.split('=')[1] || 'http://localhost:5173';\n const portArg = parseInt(argv.find((a) => a.startsWith('--port='))?.split('=')[1] || '9223', 10);\n const isHeaded = argv.includes('--headed');\n\n console.log(`š [Live] Starting background browser for ${urlArg} on CDP port ${portArg}...`);\n\n const browser = await chromium.launch({\n headless: !isHeaded,\n args: [`--remote-debugging-port=${portArg}`, '--no-sandbox']\n });\n\n const context = await browser.newContext({ viewport: { width: 1200, height: 800 } });\n const page = await context.newPage();\n await page.goto(urlArg, { waitUntil: 'domcontentloaded' });\n await page.waitForTimeout(500);\n\n saveLiveSession({\n port: portArg,\n url: urlArg,\n startedAt: new Date().toISOString()\n });\n\n await snapLive(page, { name: 'current' });\n console.log(`ā
[Live] Session active! You can now send live commands:\\n`);\n console.log(` npx agent-lens live click 450 120`);\n console.log(` npx agent-lens live type \"input\" \"hello\"`);\n console.log(` npx agent-lens live snap --full`);\n console.log(` npx agent-lens live stop\\n`);\n return true;\n }\n\n case 'click': {\n const { page, browser } = await connectToLiveSession();\n const firstArg = argv[1];\n const secondArg = argv[2];\n\n const x = parseInt(firstArg, 10);\n const y = parseInt(secondArg, 10);\n\n if (!isNaN(x) && !isNaN(y)) {\n await clickCoords(page, x, y);\n } else if (firstArg) {\n console.log(`š±ļø [Live] Clicking selector: \"${firstArg}\"`);\n await page.click(firstArg);\n } else {\n console.error(`ā Usage: npx agent-lens live click <x> <y> OR npx agent-lens live click <selector>`);\n await browser.close();\n return false;\n }\n\n await snapLive(page, { name: 'current' });\n return true;\n }\n\n case 'type': {\n const { page, browser } = await connectToLiveSession();\n const selector = argv[1];\n const text = argv[2];\n\n if (!selector || text === undefined) {\n console.error(`ā Usage: npx agent-lens live type <selector> <text>`);\n await browser.close();\n return false;\n }\n\n console.log(`āØļø [Live] Typing into \"${selector}\": \"${text}\"`);\n await page.fill(selector, text);\n await snapLive(page, { name: 'current' });\n return true;\n }\n\n case 'snap': {\n const { page } = await connectToLiveSession();\n const isFull = argv.includes('--full');\n const nameArg = argv.find((a) => !a.startsWith('--') && a !== 'snap');\n\n await snapLive(page, { name: nameArg || 'current', fullPage: isFull });\n return true;\n }\n\n case 'stop': {\n return await stopLiveSession();\n }\n\n default: {\n console.log(`\nā¹ļø AgentLens Live Controller:\n npx agent-lens live start --url=<url> Start background live session\n npx agent-lens live click <x> <y> Click at pixel coordinates\n npx agent-lens live click <selector> Click CSS selector\n npx agent-lens live type <sel> <text> Fill input field\n npx agent-lens live snap [name] [--full] Capture current or full-page screenshot\n npx agent-lens live stop Close live browser and finish\n `);\n return true;\n }\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport type { Page, Browser, BrowserContext } from 'playwright';\nimport { chromium } from '../../shared/lib/playwrightLoader';\nimport treeKill from 'tree-kill';\n\nexport interface LiveSessionInfo {\n port: number;\n url: string;\n pid?: number;\n startedAt: string;\n}\n\nconst SESSION_DIR = path.resolve(process.cwd(), '.agent-lens');\nconst SESSION_FILE = path.join(SESSION_DIR, 'live-session.json');\nconst LIVE_ARTIFACTS_DIR = path.resolve(process.cwd(), 'artifacts', 'live');\n\nexport function getSessionFile(): string {\n return SESSION_FILE;\n}\n\nexport function getLiveArtifactsDir(): string {\n if (!fs.existsSync(LIVE_ARTIFACTS_DIR)) {\n fs.mkdirSync(LIVE_ARTIFACTS_DIR, { recursive: true });\n }\n return LIVE_ARTIFACTS_DIR;\n}\n\nexport function readLiveSession(): LiveSessionInfo | null {\n if (!fs.existsSync(SESSION_FILE)) return null;\n try {\n return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nexport function saveLiveSession(info: LiveSessionInfo): void {\n if (!fs.existsSync(SESSION_DIR)) {\n fs.mkdirSync(SESSION_DIR, { recursive: true });\n }\n fs.writeFileSync(SESSION_FILE, JSON.stringify(info, null, 2), 'utf-8');\n}\n\nexport function clearLiveSession(): void {\n if (fs.existsSync(SESSION_FILE)) {\n try {\n fs.unlinkSync(SESSION_FILE);\n } catch {\n // ignore\n }\n }\n}\n\n/**\n * Connect to an already running Live Browser session over CDP.\n */\nexport async function connectToLiveSession(): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {\n const session = readLiveSession();\n if (!session) {\n throw new Error(\n `No active live session found. Start one with: npx agent-lens live start --url=http://localhost:5173`\n );\n }\n\n const browser = await chromium.connectOverCDP(`http://127.0.0.1:${session.port}`);\n const contexts = browser.contexts();\n const context = contexts[0] || (await browser.newContext());\n const pages = context.pages();\n const page = pages[0] || (await context.waitForEvent('page', { timeout: 5000 }));\n\n return { browser, context, page };\n}\n\n/**\n * Stop running live session and clean up process and session file.\n */\nexport async function stopLiveSession(): Promise<boolean> {\n const session = readLiveSession();\n if (!session) {\n console.log(`ā¹ļø [Live] No active session to stop.`);\n return false;\n }\n\n try {\n const { browser } = await connectToLiveSession();\n await browser.close().catch(() => {});\n } catch {\n // browser might already be closed\n }\n\n if (session.pid) {\n await new Promise<void>((resolve) => {\n treeKill(session.pid!, 'SIGTERM', () => resolve());\n });\n }\n\n clearLiveSession();\n console.log(`š [Live] Session stopped and cleaned up.`);\n return true;\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 { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport {\n clickCoords,\n dragAndDrop,\n scrollPercent,\n snapLive,\n handleLiveCli,\n type SnapLiveOptions\n} from './actions';\n\nexport {\n clickCoords,\n dragAndDrop,\n scrollPercent,\n snapLive,\n handleLiveCli,\n type SnapLiveOptions\n};\n\nexport const liveControllerPlugin: AgentLensPlugin = definePlugin({\n name: 'live-controller',\n version: '1.0.0',\n\n extendContext: (_ctx, page) => {\n return {\n clickCoords: async (\n x: number,\n y: number,\n options?: { button?: 'left' | 'right' | 'middle'; clickCount?: number }\n ) => {\n await clickCoords(page, x, y, options);\n },\n\n dragAndDrop: async (\n fromX: number,\n fromY: number,\n toX: number,\n toY: number,\n steps?: number\n ) => {\n await dragAndDrop(page, fromX, fromY, toX, toY, steps);\n },\n\n scrollPercent: async (percent: number) => {\n await scrollPercent(page, percent);\n },\n\n snapLive: async (options?: SnapLiveOptions) => {\n return await snapLive(page, options);\n }\n };\n }\n});\n\nexport default liveControllerPlugin;\n"],"mappings":"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","names":["path","path"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { M as MockIpcResponseType, a as MockIpcEntry, A as AgentLensPlugin } from '../../dsl-BIjVN1M0.mjs';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
declare class MockIpcRegistry {
|
|
5
|
+
private mocks;
|
|
6
|
+
set(action: string, data: any, options?: {
|
|
7
|
+
type?: MockIpcResponseType;
|
|
8
|
+
delayMs?: number;
|
|
9
|
+
}): void;
|
|
10
|
+
setBatch(entries: Array<{
|
|
11
|
+
action: string;
|
|
12
|
+
data: any;
|
|
13
|
+
type?: MockIpcResponseType;
|
|
14
|
+
delayMs?: number;
|
|
15
|
+
}>): void;
|
|
16
|
+
remove(action: string): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
get(action: string): MockIpcEntry | null;
|
|
19
|
+
toSerializable(): Record<string, {
|
|
20
|
+
data: any;
|
|
21
|
+
type: string;
|
|
22
|
+
delayMs: number;
|
|
23
|
+
}>;
|
|
24
|
+
get size(): number;
|
|
25
|
+
}
|
|
26
|
+
declare function generateMockIpcScript(registry: MockIpcRegistry): string;
|
|
27
|
+
|
|
28
|
+
declare const mockIpcPlugin: AgentLensPlugin;
|
|
29
|
+
|
|
30
|
+
export { MockIpcRegistry, mockIpcPlugin as default, generateMockIpcScript, mockIpcPlugin };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { M as MockIpcResponseType, a as MockIpcEntry, A as AgentLensPlugin } from '../../dsl-BIjVN1M0.js';
|
|
2
|
+
import 'playwright';
|
|
3
|
+
|
|
4
|
+
declare class MockIpcRegistry {
|
|
5
|
+
private mocks;
|
|
6
|
+
set(action: string, data: any, options?: {
|
|
7
|
+
type?: MockIpcResponseType;
|
|
8
|
+
delayMs?: number;
|
|
9
|
+
}): void;
|
|
10
|
+
setBatch(entries: Array<{
|
|
11
|
+
action: string;
|
|
12
|
+
data: any;
|
|
13
|
+
type?: MockIpcResponseType;
|
|
14
|
+
delayMs?: number;
|
|
15
|
+
}>): void;
|
|
16
|
+
remove(action: string): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
get(action: string): MockIpcEntry | null;
|
|
19
|
+
toSerializable(): Record<string, {
|
|
20
|
+
data: any;
|
|
21
|
+
type: string;
|
|
22
|
+
delayMs: number;
|
|
23
|
+
}>;
|
|
24
|
+
get size(): number;
|
|
25
|
+
}
|
|
26
|
+
declare function generateMockIpcScript(registry: MockIpcRegistry): string;
|
|
27
|
+
|
|
28
|
+
declare const mockIpcPlugin: AgentLensPlugin;
|
|
29
|
+
|
|
30
|
+
export { MockIpcRegistry, mockIpcPlugin as default, generateMockIpcScript, mockIpcPlugin };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/plugins/mock-ipc/index.ts
|
|
21
|
+
var mock_ipc_exports = {};
|
|
22
|
+
__export(mock_ipc_exports, {
|
|
23
|
+
MockIpcRegistry: () => MockIpcRegistry,
|
|
24
|
+
default: () => mock_ipc_default,
|
|
25
|
+
generateMockIpcScript: () => generateMockIpcScript,
|
|
26
|
+
mockIpcPlugin: () => mockIpcPlugin
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(mock_ipc_exports);
|
|
29
|
+
|
|
30
|
+
// src/shared/api/plugin.ts
|
|
31
|
+
function definePlugin(plugin) {
|
|
32
|
+
return plugin;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/plugins/mock-ipc/registry.ts
|
|
36
|
+
var MockIpcRegistry = class {
|
|
37
|
+
mocks = /* @__PURE__ */ new Map();
|
|
38
|
+
set(action, data, options) {
|
|
39
|
+
this.mocks.set(action, {
|
|
40
|
+
action,
|
|
41
|
+
data,
|
|
42
|
+
type: options?.type ?? "SUCCESS",
|
|
43
|
+
delayMs: options?.delayMs ?? 20
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
setBatch(entries) {
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
this.set(entry.action, entry.data, { type: entry.type, delayMs: entry.delayMs });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
remove(action) {
|
|
52
|
+
this.mocks.delete(action);
|
|
53
|
+
}
|
|
54
|
+
clear() {
|
|
55
|
+
this.mocks.clear();
|
|
56
|
+
}
|
|
57
|
+
get(action) {
|
|
58
|
+
return this.mocks.get(action) ?? null;
|
|
59
|
+
}
|
|
60
|
+
toSerializable() {
|
|
61
|
+
const result = {};
|
|
62
|
+
for (const [action, entry] of this.mocks.entries()) {
|
|
63
|
+
result[action] = {
|
|
64
|
+
data: entry.data,
|
|
65
|
+
type: entry.type ?? "SUCCESS",
|
|
66
|
+
delayMs: entry.delayMs ?? 20
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
get size() {
|
|
72
|
+
return this.mocks.size;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
function generateMockIpcScript(registry) {
|
|
76
|
+
const mocksJson = JSON.stringify(registry.toSerializable());
|
|
77
|
+
return `
|
|
78
|
+
(() => {
|
|
79
|
+
const __mockTable = ${mocksJson};
|
|
80
|
+
|
|
81
|
+
window.__visualRunnerMocks = __mockTable;
|
|
82
|
+
|
|
83
|
+
// Generic IPC mock bridge for modern web applications
|
|
84
|
+
window.__mockIpc = {
|
|
85
|
+
invoke: (action, payload) => {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const mock = window.__visualRunnerMocks[action];
|
|
88
|
+
|
|
89
|
+
if (mock) {
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
if (mock.type === 'ERROR') {
|
|
92
|
+
reject(new Error(mock.data));
|
|
93
|
+
} else {
|
|
94
|
+
resolve(mock.data);
|
|
95
|
+
}
|
|
96
|
+
}, mock.delayMs || 20);
|
|
97
|
+
} else {
|
|
98
|
+
console.warn('[Mock IPC] No mock for action:', action, '\u2014 returning empty SUCCESS');
|
|
99
|
+
setTimeout(() => resolve(null), 20);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Safe fallback bridge for hybrid webviews (Photino / CEF / WebView2)
|
|
106
|
+
try {
|
|
107
|
+
if (!window.external) {
|
|
108
|
+
window.external = {};
|
|
109
|
+
}
|
|
110
|
+
window.external.sendMessage = (msg) => {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = typeof msg === 'string' ? JSON.parse(msg) : msg;
|
|
113
|
+
const action = parsed.Action || parsed.action;
|
|
114
|
+
const id = parsed.Id || parsed.id;
|
|
115
|
+
const mock = window.__visualRunnerMocks[action];
|
|
116
|
+
|
|
117
|
+
if (mock) {
|
|
118
|
+
const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
const cb = window.__mockCallback;
|
|
121
|
+
if (typeof cb === 'function') cb(JSON.stringify(response));
|
|
122
|
+
}, mock.delayMs || 20);
|
|
123
|
+
} else {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
const cb = window.__mockCallback;
|
|
126
|
+
if (typeof cb === 'function') cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
|
|
127
|
+
}, 20);
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error('[Mock IPC] Failed to process message:', e);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
window.external.receiveMessage = (callback) => {
|
|
135
|
+
window.__mockCallback = callback;
|
|
136
|
+
};
|
|
137
|
+
} catch {
|
|
138
|
+
// Ignored if window.external is read-only in strict Chromium sandboxes
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys(window.__visualRunnerMocks).length, 'mocked actions');
|
|
142
|
+
})();
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/plugins/mock-ipc/index.ts
|
|
147
|
+
var STATE_KEY = "mockIpcRegistry";
|
|
148
|
+
var mockIpcPlugin = definePlugin({
|
|
149
|
+
name: "mock-ipc",
|
|
150
|
+
version: "1.0.0",
|
|
151
|
+
setup: (hookContext) => {
|
|
152
|
+
const registry = new MockIpcRegistry();
|
|
153
|
+
hookContext.state.set(STATE_KEY, registry);
|
|
154
|
+
const scenarioMocks = hookContext.scenario?.mockIpc || [];
|
|
155
|
+
const globalMocks = hookContext.cliOptions?.globalMocks || [];
|
|
156
|
+
const allMocks = [...globalMocks, ...scenarioMocks];
|
|
157
|
+
if (allMocks.length > 0) {
|
|
158
|
+
console.log(`\u{1F4E6} [Plugin:mock-ipc] Pre-loading ${allMocks.length} initial IPC mocks`);
|
|
159
|
+
registry.setBatch(allMocks);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
onContextCreated: async (context, hookContext) => {
|
|
163
|
+
const registry = hookContext.state.get(STATE_KEY) || new MockIpcRegistry();
|
|
164
|
+
const script = generateMockIpcScript(registry);
|
|
165
|
+
await context.addInitScript(script);
|
|
166
|
+
console.log(`\u{1F50C} [Plugin:mock-ipc] Injected window.__mockIpc & window.external bridge into browser context`);
|
|
167
|
+
},
|
|
168
|
+
extendContext: (_ctx, page, hookContext) => {
|
|
169
|
+
const registry = hookContext.state.get(STATE_KEY) || new MockIpcRegistry();
|
|
170
|
+
return {
|
|
171
|
+
setMockIpc: async (action, data, options) => {
|
|
172
|
+
const preview = typeof data === "string" ? data : JSON.stringify(data).slice(0, 80);
|
|
173
|
+
console.log(`\u{1F4E6} [Plugin:mock-ipc] Dynamic set: ${action} -> ${preview}`);
|
|
174
|
+
registry.set(action, data, options);
|
|
175
|
+
await page.evaluate(
|
|
176
|
+
({ action: action2, mock }) => {
|
|
177
|
+
const w = window;
|
|
178
|
+
if (!w.__visualRunnerMocks) {
|
|
179
|
+
w.__visualRunnerMocks = {};
|
|
180
|
+
}
|
|
181
|
+
w.__visualRunnerMocks[action2] = mock;
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
action,
|
|
185
|
+
mock: {
|
|
186
|
+
data,
|
|
187
|
+
type: options?.type ?? "SUCCESS",
|
|
188
|
+
delayMs: options?.delayMs ?? 20
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
teardown: (hookContext) => {
|
|
196
|
+
const registry = hookContext.state.get(STATE_KEY);
|
|
197
|
+
if (registry) {
|
|
198
|
+
registry.clear();
|
|
199
|
+
hookContext.state.delete(STATE_KEY);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
var mock_ipc_default = mockIpcPlugin;
|
|
204
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
205
|
+
0 && (module.exports = {
|
|
206
|
+
MockIpcRegistry,
|
|
207
|
+
generateMockIpcScript,
|
|
208
|
+
mockIpcPlugin
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/mock-ipc/index.ts","../../../src/shared/api/plugin.ts","../../../src/plugins/mock-ipc/registry.ts"],"sourcesContent":["import { definePlugin, type AgentLensPlugin } from '../../shared/api/plugin';\nimport { MockIpcRegistry, generateMockIpcScript } from './registry';\nimport type { MockIpcResponseType } from '../../shared/types/ipc';\n\nexport { MockIpcRegistry, generateMockIpcScript };\n\nconst STATE_KEY = 'mockIpcRegistry';\n\nexport const mockIpcPlugin: AgentLensPlugin = definePlugin({\n name: 'mock-ipc',\n version: '1.0.0',\n\n setup: (hookContext) => {\n const registry = new MockIpcRegistry();\n hookContext.state.set(STATE_KEY, registry);\n\n const scenarioMocks = hookContext.scenario?.mockIpc || [];\n const globalMocks = (hookContext.cliOptions?.globalMocks as Array<{ action: string; data: unknown; type?: MockIpcResponseType; delayMs?: number }>) || [];\n const allMocks = [...globalMocks, ...scenarioMocks];\n\n if (allMocks.length > 0) {\n console.log(`š¦ [Plugin:mock-ipc] Pre-loading ${allMocks.length} initial IPC mocks`);\n registry.setBatch(allMocks);\n }\n },\n\n onContextCreated: async (context, hookContext) => {\n const registry = (hookContext.state.get(STATE_KEY) as MockIpcRegistry) || new MockIpcRegistry();\n const script = generateMockIpcScript(registry);\n await context.addInitScript(script);\n console.log(`š [Plugin:mock-ipc] Injected window.__mockIpc & window.external bridge into browser context`);\n },\n\n extendContext: (_ctx, page, hookContext) => {\n const registry = (hookContext.state.get(STATE_KEY) as MockIpcRegistry) || new MockIpcRegistry();\n\n return {\n setMockIpc: async (\n action: string,\n data: unknown,\n options?: { type?: MockIpcResponseType; delayMs?: number }\n ) => {\n const preview = typeof data === 'string' ? data : JSON.stringify(data).slice(0, 80);\n console.log(`š¦ [Plugin:mock-ipc] Dynamic set: ${action} -> ${preview}`);\n registry.set(action, data, options);\n\n await page.evaluate(\n ({ action, mock }) => {\n const w = window as unknown as { __visualRunnerMocks?: Record<string, unknown> };\n if (!w.__visualRunnerMocks) {\n w.__visualRunnerMocks = {};\n }\n w.__visualRunnerMocks[action] = mock;\n },\n {\n action,\n mock: {\n data,\n type: options?.type ?? 'SUCCESS',\n delayMs: options?.delayMs ?? 20\n }\n }\n );\n }\n };\n },\n\n teardown: (hookContext) => {\n const registry = hookContext.state.get(STATE_KEY) as MockIpcRegistry | undefined;\n if (registry) {\n registry.clear();\n hookContext.state.delete(STATE_KEY);\n }\n }\n});\n\nexport default mockIpcPlugin;\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}","import type { MockIpcResponseType, MockIpcEntry } from '../../shared/types/ipc';\n\nexport class MockIpcRegistry {\n private mocks = new Map<string, MockIpcEntry>();\n\n public set(action: string, data: any, options?: { type?: MockIpcResponseType; delayMs?: number }): void {\n this.mocks.set(action, {\n action,\n data,\n type: options?.type ?? 'SUCCESS',\n delayMs: options?.delayMs ?? 20\n });\n }\n\n public setBatch(entries: Array<{ action: string; data: any; type?: MockIpcResponseType; delayMs?: number }>): void {\n for (const entry of entries) {\n this.set(entry.action, entry.data, { type: entry.type, delayMs: entry.delayMs });\n }\n }\n\n public remove(action: string): void {\n this.mocks.delete(action);\n }\n\n public clear(): void {\n this.mocks.clear();\n }\n\n public get(action: string): MockIpcEntry | null {\n return this.mocks.get(action) ?? null;\n }\n\n public toSerializable(): Record<string, { data: any; type: string; delayMs: number }> {\n const result: Record<string, { data: any; type: string; delayMs: number }> = {};\n for (const [action, entry] of this.mocks.entries()) {\n result[action] = {\n data: entry.data,\n type: entry.type ?? 'SUCCESS',\n delayMs: entry.delayMs ?? 20\n };\n }\n return result;\n }\n\n public get size(): number {\n return this.mocks.size;\n }\n}\n\nexport function generateMockIpcScript(registry: MockIpcRegistry): string {\n const mocksJson = JSON.stringify(registry.toSerializable());\n\n return `\n (() => {\n const __mockTable = ${mocksJson};\n\n window.__visualRunnerMocks = __mockTable;\n\n // Generic IPC mock bridge for modern web applications\n window.__mockIpc = {\n invoke: (action, payload) => {\n return new Promise((resolve, reject) => {\n const mock = window.__visualRunnerMocks[action];\n\n if (mock) {\n setTimeout(() => {\n if (mock.type === 'ERROR') {\n reject(new Error(mock.data));\n } else {\n resolve(mock.data);\n }\n }, mock.delayMs || 20);\n } else {\n console.warn('[Mock IPC] No mock for action:', action, 'ā returning empty SUCCESS');\n setTimeout(() => resolve(null), 20);\n }\n });\n }\n };\n\n // Safe fallback bridge for hybrid webviews (Photino / CEF / WebView2)\n try {\n if (!window.external) {\n window.external = {};\n }\n window.external.sendMessage = (msg) => {\n try {\n const parsed = typeof msg === 'string' ? JSON.parse(msg) : msg;\n const action = parsed.Action || parsed.action;\n const id = parsed.Id || parsed.id;\n const mock = window.__visualRunnerMocks[action];\n\n if (mock) {\n const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };\n setTimeout(() => {\n const cb = window.__mockCallback;\n if (typeof cb === 'function') cb(JSON.stringify(response));\n }, mock.delayMs || 20);\n } else {\n setTimeout(() => {\n const cb = window.__mockCallback;\n if (typeof cb === 'function') cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));\n }, 20);\n }\n } catch (e) {\n console.error('[Mock IPC] Failed to process message:', e);\n }\n };\n\n window.external.receiveMessage = (callback) => {\n window.__mockCallback = callback;\n };\n } catch {\n // Ignored if window.external is read-only in strict Chromium sandboxes\n }\n\n console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys(window.__visualRunnerMocks).length, 'mocked actions');\n })();\n `;\n}\n"],"mappings":"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","names":["action"]}
|