diff --git a/playwriter/CHANGELOG.md b/playwriter/CHANGELOG.md index 9b23d6d..c7b7cf5 100644 --- a/playwriter/CHANGELOG.md +++ b/playwriter/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.0.29 + +### Patch Changes + +- **Fixed Editor/Debugger script listing after page load**: `listScripts()` and `list()` now work correctly even when called after page has loaded + - `enable()` now disables first then re-enables to force CDP to emit `scriptParsed` and `styleSheetAdded` events + - Added 100ms debounced wait for events to arrive before returning + - `listScripts()` and `list()` are now async and auto-call `enable()` + ## 0.0.28 ### Patch Changes diff --git a/playwriter/package.json b/playwriter/package.json index f774f4d..c2d0a72 100644 --- a/playwriter/package.json +++ b/playwriter/package.json @@ -1,7 +1,7 @@ { "name": "playwriter", "description": "", - "version": "0.0.28", + "version": "0.0.29", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/playwriter/src/debugger-examples.ts b/playwriter/src/debugger-examples.ts index 40652ec..ae41fa9 100644 --- a/playwriter/src/debugger-examples.ts +++ b/playwriter/src/debugger-examples.ts @@ -6,7 +6,7 @@ async function listScriptsAndSetBreakpoint() { const dbg = createDebugger({ cdp }) await dbg.enable() - const scripts = dbg.listScripts({ search: 'app' }) + const scripts = await dbg.listScripts({ search: 'app' }) console.log(scripts) if (scripts.length > 0) { diff --git a/playwriter/src/debugger.ts b/playwriter/src/debugger.ts index d613820..06ac4bf 100644 --- a/playwriter/src/debugger.ts +++ b/playwriter/src/debugger.ts @@ -108,9 +108,28 @@ export class Debugger { if (this.debuggerEnabled) { return } + await this.cdp.send('Debugger.disable') + await this.cdp.send('Runtime.disable') + this.scripts.clear() + const scriptsReady = new Promise((resolve) => { + let timeout: ReturnType + const listener = () => { + clearTimeout(timeout) + timeout = setTimeout(() => { + this.cdp.off('Debugger.scriptParsed', listener) + resolve() + }, 100) + } + this.cdp.on('Debugger.scriptParsed', listener) + timeout = setTimeout(() => { + this.cdp.off('Debugger.scriptParsed', listener) + resolve() + }, 100) + }) await this.cdp.send('Debugger.enable') await this.cdp.send('Runtime.enable') await this.cdp.send('Runtime.runIfWaitingForDebugger') + await scriptsReady this.debuggerEnabled = true } @@ -493,8 +512,7 @@ export class Debugger { /** * Lists available scripts where breakpoints can be set. - * Scripts are collected from Debugger.scriptParsed events after enable() is called. - * Reload the page after enabling to capture all scripts. + * Automatically enables the debugger if not already enabled. * * @param options - Options * @param options.search - Optional string to filter scripts by URL (case-insensitive) @@ -503,15 +521,16 @@ export class Debugger { * @example * ```ts * // List all scripts - * const scripts = dbg.listScripts() + * const scripts = await dbg.listScripts() * // [{ scriptId: '1', url: 'https://example.com/app.js' }, ...] * * // Search for specific files - * const handlers = dbg.listScripts({ search: 'handler' }) + * const handlers = await dbg.listScripts({ search: 'handler' }) * // [{ scriptId: '5', url: 'https://example.com/handlers.js' }] * ``` */ - listScripts({ search }: { search?: string } = {}): ScriptInfo[] { + async listScripts({ search }: { search?: string } = {}): Promise { + await this.enable() const scripts = Array.from(this.scripts.values()) const filtered = search ? scripts.filter((s) => s.url.toLowerCase().includes(search.toLowerCase())) diff --git a/playwriter/src/editor-examples.ts b/playwriter/src/editor-examples.ts index 662e2f8..e3002ef 100644 --- a/playwriter/src/editor-examples.ts +++ b/playwriter/src/editor-examples.ts @@ -107,7 +107,7 @@ async function readStylesheet() { const editor = createEditor({ cdp }) await editor.enable() - const stylesheets = editor.list({ pattern: /\.css/ }) + const stylesheets = await editor.list({ pattern: /\.css/ }) console.log('Stylesheets:', stylesheets) if (stylesheets.length > 0) { diff --git a/playwriter/src/editor.ts b/playwriter/src/editor.ts index c0f9e34..e67abaf 100644 --- a/playwriter/src/editor.ts +++ b/playwriter/src/editor.ts @@ -86,9 +86,33 @@ export class Editor { if (this.enabled) { return } + await this.cdp.send('Debugger.disable') + await this.cdp.send('CSS.disable') + this.scripts.clear() + this.stylesheets.clear() + this.sourceCache.clear() + const resourcesReady = new Promise((resolve) => { + let timeout: ReturnType + const listener = () => { + clearTimeout(timeout) + timeout = setTimeout(() => { + this.cdp.off('Debugger.scriptParsed', listener) + this.cdp.off('CSS.styleSheetAdded', listener) + resolve() + }, 100) + } + this.cdp.on('Debugger.scriptParsed', listener) + this.cdp.on('CSS.styleSheetAdded', listener) + timeout = setTimeout(() => { + this.cdp.off('Debugger.scriptParsed', listener) + this.cdp.off('CSS.styleSheetAdded', listener) + resolve() + }, 100) + }) await this.cdp.send('Debugger.enable') await this.cdp.send('DOM.enable') await this.cdp.send('CSS.enable') + await resourcesReady this.enabled = true } @@ -108,6 +132,7 @@ export class Editor { /** * Lists available script and stylesheet URLs. Use pattern to filter by regex. + * Automatically enables the editor if not already enabled. * * @param options - Options * @param options.pattern - Optional regex to filter URLs @@ -116,19 +141,20 @@ export class Editor { * @example * ```ts * // List all scripts and stylesheets - * const urls = editor.list() + * const urls = await editor.list() * * // List only JS files - * const jsFiles = editor.list({ pattern: /\.js/ }) + * const jsFiles = await editor.list({ pattern: /\.js/ }) * * // List only CSS files - * const cssFiles = editor.list({ pattern: /\.css/ }) + * const cssFiles = await editor.list({ pattern: /\.css/ }) * * // Search for specific scripts - * const appScripts = editor.list({ pattern: /app/ }) + * const appScripts = await editor.list({ pattern: /app/ }) * ``` */ - list({ pattern }: { pattern?: RegExp } = {}): string[] { + async list({ pattern }: { pattern?: RegExp } = {}): Promise { + await this.enable() const urls = [...Array.from(this.scripts.keys()), ...Array.from(this.stylesheets.keys())] if (!pattern) { @@ -319,7 +345,7 @@ export class Editor { await this.enable() const matches: SearchMatch[] = [] - const urls = this.list({ pattern }) + const urls = await this.list({ pattern }) for (const url of urls) { let source: string diff --git a/playwriter/src/mcp.test.ts b/playwriter/src/mcp.test.ts index 5e32b70..8cb1a2d 100644 --- a/playwriter/src/mcp.test.ts +++ b/playwriter/src/mcp.test.ts @@ -2094,19 +2094,12 @@ describe('CDP Session Tests', () => { const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl }) const dbg = new Debugger({ cdp: cdpSession }) - await dbg.enable() - - for (let i = 0; i < 20; i++) { - if (dbg.listScripts().length > 0) break - await new Promise(r => setTimeout(r, 100)) - } - - const scripts = dbg.listScripts() + const scripts = await dbg.listScripts() expect(scripts.length).toBeGreaterThan(0) expect(scripts[0]).toHaveProperty('scriptId') expect(scripts[0]).toHaveProperty('url') - const dataScripts = dbg.listScripts({ search: 'data:' }) + const dataScripts = await dbg.listScripts({ search: 'data:' }) expect(dataScripts.length).toBeGreaterThan(0) cdpSession.close() @@ -2698,8 +2691,7 @@ describe('CDP Session Tests', () => { `, }) await new Promise(r => setTimeout(r, 100)) - - const scripts = editor.list() + const scripts = await editor.list() expect(scripts.length).toBeGreaterThan(0) const matches = await editor.grep({ regex: /greetUser/ }) @@ -2766,8 +2758,7 @@ describe('CDP Session Tests', () => { `, }) await new Promise(r => setTimeout(r, 100)) - - const stylesheets = editor.list({ pattern: /inline-css:/ }) + const stylesheets = await editor.list({ pattern: /inline-css:/ }) expect(stylesheets.length).toBeGreaterThan(0) const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ })