document debugger

This commit is contained in:
Tommy D. Rossi
2025-12-25 21:20:15 +01:00
parent 918253dbd6
commit ccffd9477d
5 changed files with 124 additions and 7 deletions
+1 -1
View File
@@ -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",
@@ -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<CDPSession>
export declare const createDebugger: (options: { cdp: CDPSession }) => Debugger
export declare const console: { log: (...args: unknown[]) => void }
+66
View File
@@ -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 }
+40
View File
@@ -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<WaitForPageLoadResult>
getCDPSession: (options: { page: Page }) => Promise<CDPSession>
createDebugger: (options: { cdp: CDPSession }) => Debugger
require: NodeRequire
import: (specifier: string) => Promise<any>
}
@@ -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
+9 -6
View File
@@ -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: