From ccffd9477d95a04d4fd31bb9edbc54d16aa9b086 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Thu, 25 Dec 2025 21:20:15 +0100 Subject: [PATCH] document debugger --- playwriter/package.json | 2 +- playwriter/src/debugger-examples-types.ts | 8 +++ playwriter/src/debugger-examples.ts | 66 +++++++++++++++++++++++ playwriter/src/mcp.ts | 40 ++++++++++++++ playwriter/src/prompt.md | 15 +++--- 5 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 playwriter/src/debugger-examples-types.ts create mode 100644 playwriter/src/debugger-examples.ts diff --git a/playwriter/package.json b/playwriter/package.json index 75710e3..75e729f 100644 --- a/playwriter/package.json +++ b/playwriter/package.json @@ -7,7 +7,7 @@ "types": "dist/index.d.ts", "repository": "https://github.com/remorses/playwriter", "scripts": { - "build": "rm -rf dist *.tsbuildinfo && mkdir dist && cp src/resource.md src/prompt.md dist/ && vite-node scripts/download-selector-generator.ts && tsc", + "build": "rm -rf dist *.tsbuildinfo && mkdir dist && cp src/resource.md src/prompt.md src/debugger-examples.ts dist/ && vite-node scripts/download-selector-generator.ts && tsc", "prepublishOnly": "pnpm build", "watch": "tsc -w", "typecheck": "tsc --noEmit", diff --git a/playwriter/src/debugger-examples-types.ts b/playwriter/src/debugger-examples-types.ts new file mode 100644 index 0000000..5b445ea --- /dev/null +++ b/playwriter/src/debugger-examples-types.ts @@ -0,0 +1,8 @@ +import type { Page } from 'playwright-core' +import type { CDPSession } from './cdp-session.js' +import type { Debugger } from './debugger.js' + +export declare const page: Page +export declare const getCDPSession: (options: { page: Page }) => Promise +export declare const createDebugger: (options: { cdp: CDPSession }) => Debugger +export declare const console: { log: (...args: unknown[]) => void } diff --git a/playwriter/src/debugger-examples.ts b/playwriter/src/debugger-examples.ts new file mode 100644 index 0000000..938ef33 --- /dev/null +++ b/playwriter/src/debugger-examples.ts @@ -0,0 +1,66 @@ +import { page, getCDPSession, createDebugger, console } from './debugger-examples-types.js' + +// Example: List available scripts and set a breakpoint +async function listScriptsAndSetBreakpoint() { + const cdp = await getCDPSession({ page }) + const dbg = createDebugger({ cdp }) + await dbg.enable() + + const scripts = dbg.listScripts({ search: 'app' }) + console.log(scripts) + + if (scripts.length > 0) { + const bpId = await dbg.setBreakpoint({ file: scripts[0].url, line: 100 }) + console.log('Breakpoint set:', bpId) + } +} + +// Example: Inspect state when paused at a breakpoint +async function inspectWhenPaused() { + const cdp = await getCDPSession({ page }) + const dbg = createDebugger({ cdp }) + await dbg.enable() + + if (dbg.isPaused()) { + const loc = await dbg.getLocation() + console.log('Paused at:', loc.url, 'line', loc.lineNumber) + console.log('Source:', loc.sourceContext) + + const vars = await dbg.inspectVariables({ scope: 'local' }) + console.log('Variables:', vars) + + const result = await dbg.evaluate({ expression: 'myVar.length' }) + console.log('myVar.length =', result.value) + + await dbg.stepOver() + } +} + +// Example: Step through code +async function stepThroughCode() { + const cdp = await getCDPSession({ page }) + const dbg = createDebugger({ cdp }) + await dbg.enable() + + await dbg.setBreakpoint({ file: 'https://example.com/app.js', line: 42 }) + + if (dbg.isPaused()) { + await dbg.stepOver() + await dbg.stepInto() + await dbg.stepOut() + await dbg.resume() + } +} + +// Example: Cleanup all breakpoints +async function cleanupBreakpoints() { + const cdp = await getCDPSession({ page }) + const dbg = createDebugger({ cdp }) + + const breakpoints = dbg.listBreakpoints() + for (const bp of breakpoints) { + await dbg.deleteBreakpoint({ breakpointId: bp.id }) + } +} + +export { listScriptsAndSetBreakpoint, inspectWhenPaused, stepThroughCode, cleanupBreakpoints } diff --git a/playwriter/src/mcp.ts b/playwriter/src/mcp.ts index 4d9e195..721da31 100644 --- a/playwriter/src/mcp.ts +++ b/playwriter/src/mcp.ts @@ -15,6 +15,7 @@ import { getCdpUrl, LOG_FILE_PATH, VERSION, sleep } from './utils.js' import { killPortProcess } from 'kill-port-process' import { waitForPageLoad, WaitForPageLoadOptions, WaitForPageLoadResult } from './wait-for-page-load.js' import { getCDPSessionForPage, CDPSession } from './cdp-session.js' +import { Debugger } from './debugger.js' class CodeExecutionTimeoutError extends Error { constructor(timeout: number) { @@ -72,6 +73,7 @@ interface VMContext { clearAllLogs: () => void waitForPageLoad: (options: WaitForPageLoadOptions) => Promise getCDPSession: (options: { page: Page }) => Promise + createDebugger: (options: { cdp: CDPSession }) => Debugger require: NodeRequire import: (specifier: string) => Promise } @@ -389,6 +391,38 @@ const promptContent = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'prompt.md'), 'utf-8') + `\n\nfor debugging internal playwriter errors, check playwriter relay server logs at: ${LOG_FILE_PATH}` +server.resource('debugger-api', 'playwriter://debugger-api', { mimeType: 'text/plain' }, async () => { + const packageJsonPath = require.resolve('playwriter/package.json') + const distDir = path.join(path.dirname(packageJsonPath), 'dist') + + const debuggerTypes = fs.readFileSync(path.join(distDir, 'debugger.d.ts'), 'utf-8') + const debuggerExamples = fs.readFileSync(path.join(distDir, 'debugger-examples.ts'), 'utf-8') + + return { + contents: [ + { + uri: 'playwriter://debugger-api', + text: dedent` + # Debugger API Reference + + ## Types + + \`\`\`ts + ${debuggerTypes} + \`\`\` + + ## Examples + + \`\`\`ts + ${debuggerExamples} + \`\`\` + `, + mimeType: 'text/plain', + }, + ], + } +}) + server.tool( 'execute', promptContent, @@ -584,6 +618,10 @@ server.tool( return session } + const createDebugger = (options: { cdp: CDPSession }) => { + return new Debugger(options) + } + let vmContextObj: VMContextWithGlobals = { page, context, @@ -595,6 +633,7 @@ server.tool( clearAllLogs, waitForPageLoad, getCDPSession, + createDebugger, resetPlaywright: async () => { const { page: newPage, context: newContext } = await resetConnection() @@ -609,6 +648,7 @@ server.tool( clearAllLogs, waitForPageLoad, getCDPSession, + createDebugger, resetPlaywright: vmContextObj.resetPlaywright, require, // TODO --experimental-vm-modules is needed to make import work in vm diff --git a/playwriter/src/prompt.md b/playwriter/src/prompt.md index 453577b..333daad 100644 --- a/playwriter/src/prompt.md +++ b/playwriter/src/prompt.md @@ -95,13 +95,16 @@ you have access to some functions in addition to playwright methods: - `page`: the page object to create the session for - Returns: `{ send(method, params?), on(event, callback), off(event, callback) }` - Example: `const cdp = await getCDPSession({ page }); const metrics = await cdp.send('Page.getLayoutMetrics');` - - Example listening for events: +- `createDebugger({ cdp })`: creates a Debugger instance for setting breakpoints, stepping, and inspecting variables. Works with browser JS or Node.js (--inspect). + - `cdp`: a CDPSession from `getCDPSession` + - Methods: `enable()`, `setBreakpoint({ file, line })`, `deleteBreakpoint({ breakpointId })`, `listBreakpoints()`, `listScripts({ search? })`, `evaluate({ expression })`, `executeCode({ code })`, `inspectVariables({ scope? })`, `getLocation()`, `stepOver()`, `stepInto()`, `stepOut()`, `resume()`, `getConsoleOutput({ limit? })`, `isPaused()` + - Example: ```js - const cdp = await getCDPSession({ page }); - await cdp.send('Debugger.enable'); - const pausedEvent = await new Promise((resolve) => { cdp.on('Debugger.paused', resolve); }); - console.log('Paused at:', pausedEvent.callFrames[0].location); - await cdp.send('Debugger.resume'); + const cdp = await getCDPSession({ page }); const dbg = createDebugger({ cdp }); await dbg.enable(); + console.log(dbg.listScripts({ search: 'app' })); + await dbg.setBreakpoint({ file: 'https://example.com/app.js', line: 42 }); + // user triggers the code, then: + if (dbg.isPaused()) { console.log(await dbg.getLocation()); console.log(await dbg.inspectVariables()); await dbg.resume(); } ``` example: