playwriter 0.0.16 → 0.0.21

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.
Files changed (47) hide show
  1. package/dist/cdp-session.d.ts +21 -0
  2. package/dist/cdp-session.d.ts.map +1 -0
  3. package/dist/cdp-session.js +131 -0
  4. package/dist/cdp-session.js.map +1 -0
  5. package/dist/cdp-types.d.ts +15 -0
  6. package/dist/cdp-types.d.ts.map +1 -1
  7. package/dist/cdp-types.js.map +1 -1
  8. package/dist/create-logger.d.ts +9 -0
  9. package/dist/create-logger.d.ts.map +1 -0
  10. package/dist/create-logger.js +43 -0
  11. package/dist/create-logger.js.map +1 -0
  12. package/dist/extension/cdp-relay.d.ts +7 -3
  13. package/dist/extension/cdp-relay.d.ts.map +1 -1
  14. package/dist/extension/cdp-relay.js +22 -12
  15. package/dist/extension/cdp-relay.js.map +1 -1
  16. package/dist/mcp.js +86 -44
  17. package/dist/mcp.js.map +1 -1
  18. package/dist/mcp.test.d.ts.map +1 -1
  19. package/dist/mcp.test.js +669 -183
  20. package/dist/mcp.test.js.map +1 -1
  21. package/dist/prompt.md +38 -8
  22. package/dist/selector-generator.js +331 -0
  23. package/dist/start-relay-server.d.ts +1 -3
  24. package/dist/start-relay-server.d.ts.map +1 -1
  25. package/dist/start-relay-server.js +3 -16
  26. package/dist/start-relay-server.js.map +1 -1
  27. package/dist/utils.d.ts +3 -0
  28. package/dist/utils.d.ts.map +1 -1
  29. package/dist/utils.js +36 -0
  30. package/dist/utils.js.map +1 -1
  31. package/dist/wait-for-page-load.d.ts +16 -0
  32. package/dist/wait-for-page-load.d.ts.map +1 -0
  33. package/dist/wait-for-page-load.js +126 -0
  34. package/dist/wait-for-page-load.js.map +1 -0
  35. package/package.json +16 -12
  36. package/src/cdp-session.ts +156 -0
  37. package/src/cdp-types.ts +6 -0
  38. package/src/create-logger.ts +56 -0
  39. package/src/debugger.md +453 -0
  40. package/src/extension/cdp-relay.ts +32 -14
  41. package/src/mcp.test.ts +795 -189
  42. package/src/mcp.ts +101 -47
  43. package/src/prompt.md +38 -8
  44. package/src/snapshots/shadcn-ui-accessibility.md +94 -91
  45. package/src/start-relay-server.ts +3 -20
  46. package/src/utils.ts +45 -0
  47. package/src/wait-for-page-load.ts +173 -0
@@ -0,0 +1,453 @@
1
+ # JavaScript Debugging with CDP
2
+
3
+ You can use the Chrome DevTools Protocol (CDP) Debugger domain to debug JavaScript in the browser. This allows you to set breakpoints, step through code, inspect variables, and examine stack traces.
4
+
5
+ ## Getting Started
6
+
7
+ Use `getCDPSession` to access CDP commands:
8
+
9
+ ```js
10
+ const cdp = await getCDPSession({ page });
11
+ await cdp.send('Debugger.enable');
12
+ ```
13
+
14
+ Always enable the debugger before using other debugger commands.
15
+
16
+ ## Listing Scripts on the Page
17
+
18
+ Use `Page.getResourceTree` to list all loaded resources including scripts:
19
+
20
+ ```js
21
+ const cdp = await getCDPSession({ page });
22
+ const tree = await cdp.send('Page.getResourceTree');
23
+ const scripts = tree.frameTree.resources.filter(r => r.type === 'Script');
24
+ scripts.forEach(s => { console.log(s.url); });
25
+ ```
26
+
27
+ The response structure:
28
+
29
+ ```js
30
+ {
31
+ "frameTree": {
32
+ "frame": {
33
+ "id": "FRAME_ID",
34
+ "url": "https://example.com/page",
35
+ "securityOrigin": "https://example.com",
36
+ "mimeType": "text/html"
37
+ },
38
+ "resources": [
39
+ {
40
+ "url": "https://example.com/app.js",
41
+ "type": "Script", // Script, Image, Font, Stylesheet, etc.
42
+ "mimeType": "text/javascript",
43
+ "contentSize": 78679, // size in bytes
44
+ "lastModified": 1765390319 // timestamp
45
+ }
46
+ ]
47
+ }
48
+ }
49
+ ```
50
+
51
+ Filter to find app scripts (exclude libraries):
52
+
53
+ ```js
54
+ const cdp = await getCDPSession({ page });
55
+ const tree = await cdp.send('Page.getResourceTree');
56
+ const scripts = tree.frameTree.resources.filter(r => r.type === 'Script' && !r.url.includes('vendor') && !r.url.includes('node_modules'));
57
+ scripts.forEach(s => { console.log(`${(s.contentSize / 1024).toFixed(1)}KB - ${s.url.split('/').pop()}`); });
58
+ ```
59
+
60
+ ## Understanding Call Frames
61
+
62
+ When the debugger pauses, you get an array of **call frames** representing the call stack. Frame 0 is where execution paused, frame 1 is the function that called it, frame 2 called frame 1, and so on.
63
+
64
+ Example: If you have `outer()` → `middle()` → `inner()` → `debugger`, you'll see:
65
+
66
+ ```
67
+ #0 inner ← where we paused
68
+ #1 middle ← called inner()
69
+ #2 outer ← called middle()
70
+ #3 (anonymous) ← top-level code that called outer()
71
+ ```
72
+
73
+ ### Call Frame Structure
74
+
75
+ Each call frame contains:
76
+
77
+ ```js
78
+ {
79
+ "callFrameId": "-4687593592348163175.1.0", // ID for evaluating in this frame
80
+ "functionName": "inner", // function name (empty for anonymous)
81
+ "canBeRestarted": true, // can restart from this frame
82
+ "location": {
83
+ "scriptId": "1104",
84
+ "lineNumber": 0,
85
+ "columnNumber": 116
86
+ },
87
+ "functionLocation": { // where function is defined
88
+ "scriptId": "1104",
89
+ "lineNumber": 0,
90
+ "columnNumber": 110
91
+ },
92
+ "scopeChain": [ // variables in scope
93
+ {
94
+ "type": "local", // local, closure, global, block, catch, etc.
95
+ "name": "inner",
96
+ "object": { "objectId": "..." } // use Runtime.getProperties to inspect
97
+ },
98
+ { "type": "closure", "object": { "objectId": "..." } },
99
+ { "type": "global", "object": { "objectId": "..." } }
100
+ ],
101
+ "this": { // 'this' value in this frame
102
+ "type": "object",
103
+ "className": "Window"
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Inspecting the Call Stack
109
+
110
+ Print the full call stack when paused:
111
+
112
+ ```js
113
+ state.lastPause.callFrames.forEach((frame, i) => {
114
+ console.log(`#${i} ${frame.functionName || '(anonymous)'} at line ${frame.location.lineNumber}`);
115
+ console.log(` Scopes: ${frame.scopeChain.map(s => s.type).join(', ')}`);
116
+ });
117
+ ```
118
+
119
+ ### Getting Local Variables from a Frame
120
+
121
+ ```js
122
+ const cdp = await getCDPSession({ page });
123
+ const frame = state.lastPause.callFrames[0];
124
+ const localScope = frame.scopeChain.find(s => s.type === 'local');
125
+ const props = await cdp.send('Runtime.getProperties', { objectId: localScope.object.objectId });
126
+ props.result.forEach(p => { console.log(`${p.name} = ${p.value?.value}`); });
127
+ ```
128
+
129
+ ### Evaluating in Different Frames
130
+
131
+ You can evaluate expressions in any frame, not just the current one:
132
+
133
+ ```js
134
+ const cdp = await getCDPSession({ page });
135
+ // Evaluate in the parent frame (frame 1)
136
+ const parentFrameId = state.lastPause.callFrames[1].callFrameId;
137
+ const result = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: parentFrameId, expression: 'myVar' });
138
+ console.log('Value in parent frame:', result.result.value);
139
+ ```
140
+
141
+ ## Example Flows
142
+
143
+ ### Flow 1: Catch a `debugger` Statement in Code
144
+
145
+ If the site has `debugger;` statements or you want to pause on them:
146
+
147
+ ```js
148
+ const cdp = await getCDPSession({ page });
149
+ await cdp.send('Debugger.enable');
150
+ state.pauseHandler = (event) => { state.lastPause = event; };
151
+ cdp.on('Debugger.paused', state.pauseHandler);
152
+ ```
153
+
154
+ Then trigger the code that contains `debugger;`. After it pauses:
155
+
156
+ ```js
157
+ console.log('Paused at:', state.lastPause.callFrames[0].location);
158
+ console.log('Reason:', state.lastPause.reason);
159
+ ```
160
+
161
+ Resume execution:
162
+
163
+ ```js
164
+ const cdp = await getCDPSession({ page }); await cdp.send('Debugger.resume');
165
+ ```
166
+
167
+ ### Flow 2: Set a Breakpoint by URL and Line Number
168
+
169
+ Set a breakpoint in a specific file:
170
+
171
+ ```js
172
+ const cdp = await getCDPSession({ page });
173
+ await cdp.send('Debugger.enable');
174
+ const result = await cdp.send('Debugger.setBreakpointByUrl', { lineNumber: 42, urlRegex: '.*app\\.js$' });
175
+ console.log('Breakpoint ID:', result.breakpointId);
176
+ console.log('Resolved locations:', result.locations);
177
+ ```
178
+
179
+ ### Flow 3: Pause on Exceptions
180
+
181
+ Catch all exceptions (or only uncaught ones):
182
+
183
+ ```js
184
+ const cdp = await getCDPSession({ page });
185
+ await cdp.send('Debugger.enable');
186
+ await cdp.send('Debugger.setPauseOnExceptions', { state: 'all' }); // 'none', 'caught', 'uncaught', 'all'
187
+ state.pauseHandler = (event) => { state.lastPause = event; };
188
+ cdp.on('Debugger.paused', state.pauseHandler);
189
+ ```
190
+
191
+ Now when an exception is thrown, the debugger pauses. Check what happened:
192
+
193
+ ```js
194
+ console.log('Exception paused:', state.lastPause.reason);
195
+ console.log('Data:', JSON.stringify(state.lastPause.data, null, 2));
196
+ ```
197
+
198
+ ### Flow 4: Inspect the Call Stack
199
+
200
+ When paused, examine the stack trace:
201
+
202
+ ```js
203
+ const frames = state.lastPause.callFrames;
204
+ frames.forEach((frame, i) => {
205
+ console.log(`#${i} ${frame.functionName || '(anonymous)'} at ${frame.url}:${frame.location.lineNumber}:${frame.location.columnNumber}`);
206
+ });
207
+ ```
208
+
209
+ ### Flow 5: Evaluate Variables in a Paused Frame
210
+
211
+ When paused, you can evaluate expressions in any call frame:
212
+
213
+ ```js
214
+ const cdp = await getCDPSession({ page });
215
+ const frameId = state.lastPause.callFrames[0].callFrameId;
216
+ const result = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: frameId, expression: 'myVariable' });
217
+ console.log('Value:', result.result.value);
218
+ console.log('Type:', result.result.type);
219
+ ```
220
+
221
+ Evaluate complex expressions:
222
+
223
+ ```js
224
+ const cdp = await getCDPSession({ page });
225
+ const frameId = state.lastPause.callFrames[0].callFrameId;
226
+ const result = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: frameId, expression: 'JSON.stringify({ user, config, state })' });
227
+ console.log('Context:', result.result.value);
228
+ ```
229
+
230
+ ### Flow 6: Inspect Scope Variables
231
+
232
+ Each call frame has a scope chain. List all variables in scope:
233
+
234
+ ```js
235
+ const frame = state.lastPause.callFrames[0];
236
+ frame.scopeChain.forEach((scope, i) => {
237
+ console.log(`Scope ${i}: ${scope.type} ${scope.name || ''}`);
238
+ });
239
+ ```
240
+
241
+ To get variables from a specific scope, use `Runtime.getProperties` on the scope's object:
242
+
243
+ ```js
244
+ const cdp = await getCDPSession({ page });
245
+ const localScope = state.lastPause.callFrames[0].scopeChain.find(s => s.type === 'local');
246
+ if (localScope) {
247
+ const props = await cdp.send('Runtime.getProperties', { objectId: localScope.object.objectId });
248
+ props.result.forEach(p => { console.log(` ${p.name}: ${p.value?.value ?? p.value?.type}`); });
249
+ }
250
+ ```
251
+
252
+ ### Flow 7: Step Through Code
253
+
254
+ When paused, use stepping commands:
255
+
256
+ ```js
257
+ const cdp = await getCDPSession({ page });
258
+ await cdp.send('Debugger.stepOver'); // Execute current line, don't enter functions
259
+ await cdp.send('Debugger.stepInto'); // Step into function calls
260
+ await cdp.send('Debugger.stepOut'); // Run until current function returns
261
+ ```
262
+
263
+ After each step, the `Debugger.paused` event fires again with the new location.
264
+
265
+ ### Flow 8: Get Script Source
266
+
267
+ When you see a scriptId in the pause event, get its source:
268
+
269
+ ```js
270
+ const cdp = await getCDPSession({ page });
271
+ const scriptId = state.lastPause.callFrames[0].location.scriptId;
272
+ const source = await cdp.send('Debugger.getScriptSource', { scriptId });
273
+ const lines = source.scriptSource.split('\n');
274
+ const line = state.lastPause.callFrames[0].location.lineNumber;
275
+ console.log('Current line:', lines[line]);
276
+ console.log('Context:', lines.slice(Math.max(0, line - 3), line + 4).join('\n'));
277
+ ```
278
+
279
+ ### Flow 9: Search in Script Content
280
+
281
+ Find where a function or variable is used:
282
+
283
+ ```js
284
+ const cdp = await getCDPSession({ page });
285
+ const scriptId = state.lastPause.callFrames[0].location.scriptId;
286
+ const matches = await cdp.send('Debugger.searchInContent', { scriptId, query: 'handleClick', isRegex: false });
287
+ matches.result.forEach(m => { console.log(`Line ${m.lineNumber}: ${m.lineContent}`); });
288
+ ```
289
+
290
+ ### Flow 10: Watch a Variable Across Steps
291
+
292
+ Store variable values as you step through:
293
+
294
+ ```js
295
+ state.watchedValues = state.watchedValues || [];
296
+ const cdp = await getCDPSession({ page });
297
+ const frameId = state.lastPause.callFrames[0].callFrameId;
298
+ const result = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: frameId, expression: 'counter' });
299
+ state.watchedValues.push({ line: state.lastPause.callFrames[0].location.lineNumber, value: result.result.value });
300
+ console.log('Watch history:', state.watchedValues);
301
+ ```
302
+
303
+ ### Flow 11: Modify a Variable at Runtime
304
+
305
+ Change a variable's value while paused:
306
+
307
+ ```js
308
+ const cdp = await getCDPSession({ page });
309
+ const frameId = state.lastPause.callFrames[0].callFrameId;
310
+ await cdp.send('Debugger.setVariableValue', {
311
+ scopeNumber: 0, // 0 = local scope
312
+ variableName: 'shouldRetry',
313
+ newValue: { value: true },
314
+ callFrameId: frameId
315
+ });
316
+ console.log('Variable modified');
317
+ ```
318
+
319
+ ### Flow 12: Skip All Pauses Temporarily
320
+
321
+ Disable breakpoints without removing them:
322
+
323
+ ```js
324
+ const cdp = await getCDPSession({ page }); await cdp.send('Debugger.setSkipAllPauses', { skip: true });
325
+ ```
326
+
327
+ Re-enable:
328
+
329
+ ```js
330
+ const cdp = await getCDPSession({ page }); await cdp.send('Debugger.setSkipAllPauses', { skip: false });
331
+ ```
332
+
333
+ ### Flow 13: Blackbox Third-Party Scripts
334
+
335
+ Skip stepping into library code (e.g., React, lodash):
336
+
337
+ ```js
338
+ const cdp = await getCDPSession({ page });
339
+ await cdp.send('Debugger.setBlackboxPatterns', { patterns: ['.*node_modules.*', '.*vendor.*', '.*react.*'] });
340
+ ```
341
+
342
+ ### Flow 14: Continue to a Specific Location
343
+
344
+ Instead of stepping line by line, jump to a specific location:
345
+
346
+ ```js
347
+ const cdp = await getCDPSession({ page });
348
+ await cdp.send('Debugger.continueToLocation', {
349
+ location: { scriptId: 'your-script-id', lineNumber: 100 },
350
+ targetCallFrames: 'any'
351
+ });
352
+ ```
353
+
354
+ ## Complete Debugging Session Example
355
+
356
+ Here's a full example debugging a button click handler:
357
+
358
+ **Step 1: Enable debugger and set up pause listener**
359
+
360
+ ```js
361
+ const cdp = await getCDPSession({ page });
362
+ await cdp.send('Debugger.enable');
363
+ await cdp.send('Debugger.setPauseOnExceptions', { state: 'uncaught' });
364
+ state.pauseHandler = (event) => { state.lastPause = event; console.log('PAUSED:', event.reason, 'at line', event.callFrames[0]?.location?.lineNumber); };
365
+ cdp.on('Debugger.paused', state.pauseHandler);
366
+ ```
367
+
368
+ **Step 2: Set breakpoint on the handler**
369
+
370
+ ```js
371
+ const cdp = await getCDPSession({ page });
372
+ const bp = await cdp.send('Debugger.setBreakpointByUrl', { lineNumber: 25, urlRegex: '.*handleSubmit.*' });
373
+ console.log('Breakpoint set:', bp.breakpointId);
374
+ ```
375
+
376
+ **Step 3: Trigger the action (click the button)**
377
+
378
+ ```js
379
+ await page.click('#submit-button');
380
+ ```
381
+
382
+ **Step 4: When paused, inspect state**
383
+
384
+ ```js
385
+ const cdp = await getCDPSession({ page });
386
+ const frameId = state.lastPause.callFrames[0].callFrameId;
387
+ const formData = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: frameId, expression: 'JSON.stringify(formData)' });
388
+ console.log('Form data at breakpoint:', formData.result.value);
389
+ ```
390
+
391
+ **Step 5: Step through and watch**
392
+
393
+ ```js
394
+ const cdp = await getCDPSession({ page }); await cdp.send('Debugger.stepOver');
395
+ ```
396
+
397
+ **Step 6: Check new state after step**
398
+
399
+ ```js
400
+ const cdp = await getCDPSession({ page });
401
+ const frameId = state.lastPause.callFrames[0].callFrameId;
402
+ const result = await cdp.send('Debugger.evaluateOnCallFrame', { callFrameId: frameId, expression: 'validationResult' });
403
+ console.log('Validation result:', result.result.value);
404
+ ```
405
+
406
+ **Step 7: Resume and clean up**
407
+
408
+ ```js
409
+ const cdp = await getCDPSession({ page });
410
+ await cdp.send('Debugger.resume');
411
+ await cdp.send('Debugger.disable');
412
+ cdp.off('Debugger.paused', state.pauseHandler);
413
+ ```
414
+
415
+ ## CDP Debugger Command Reference
416
+
417
+ | Command | Description |
418
+ |---------|-------------|
419
+ | `Debugger.enable` | Enable debugging (required first) |
420
+ | `Debugger.disable` | Disable debugging |
421
+ | `Debugger.pause` | Pause execution immediately |
422
+ | `Debugger.resume` | Resume execution |
423
+ | `Debugger.stepOver` | Step over current statement |
424
+ | `Debugger.stepInto` | Step into function call |
425
+ | `Debugger.stepOut` | Step out of current function |
426
+ | `Debugger.setBreakpoint` | Set breakpoint at exact location |
427
+ | `Debugger.setBreakpointByUrl` | Set breakpoint by URL pattern |
428
+ | `Debugger.removeBreakpoint` | Remove a breakpoint |
429
+ | `Debugger.setPauseOnExceptions` | Pause on exceptions: `none`, `caught`, `uncaught`, `all` |
430
+ | `Debugger.evaluateOnCallFrame` | Evaluate expression in a frame |
431
+ | `Debugger.setVariableValue` | Modify variable value |
432
+ | `Debugger.getScriptSource` | Get script source code |
433
+ | `Debugger.searchInContent` | Search in script |
434
+ | `Debugger.setSkipAllPauses` | Temporarily disable all breakpoints |
435
+ | `Debugger.setBlackboxPatterns` | Skip stepping into matching scripts |
436
+
437
+ ## Events
438
+
439
+ | Event | Description |
440
+ |-------|-------------|
441
+ | `Debugger.paused` | VM paused (breakpoint, exception, step) |
442
+ | `Debugger.resumed` | VM resumed execution |
443
+ | `Debugger.scriptParsed` | New script was parsed |
444
+ | `Debugger.scriptFailedToParse` | Script parsing failed |
445
+
446
+ ## Tips
447
+
448
+ - Always call `Debugger.enable` before other debugger commands
449
+ - Store pause events in `state.lastPause` to inspect them in subsequent calls
450
+ - Use `Debugger.setBlackboxPatterns` to skip library code when stepping
451
+ - Clean up event listeners with `cdp.off()` when done debugging
452
+ - The `callFrameId` is only valid while paused - use it immediately
453
+ - Use `Debugger.evaluateOnCallFrame` with `JSON.stringify()` for complex objects
@@ -3,9 +3,10 @@ import { serve } from '@hono/node-server'
3
3
  import { createNodeWebSocket } from '@hono/node-ws'
4
4
  import type { WSContext } from 'hono/ws'
5
5
  import type { Protocol } from '../cdp-types.js'
6
- import type { CDPCommand, CDPResponseBase, CDPEventBase, CDPEventFor } from '../cdp-types.js'
6
+ import type { CDPCommand, CDPResponseBase, CDPEventBase, CDPEventFor, RelayServerEvents } from '../cdp-types.js'
7
7
  import type { ExtensionMessage, ExtensionEventMessage } from './protocol.js'
8
8
  import chalk from 'chalk'
9
+ import { EventEmitter } from 'node:events'
9
10
 
10
11
  type ConnectedTarget = {
11
12
  sessionId: string
@@ -21,7 +22,14 @@ type PlaywrightClient = {
21
22
  }
22
23
 
23
24
 
24
- export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.0.0.1', logger }: { port?: number; host?: string; logger?: { log(...args: any[]): void; error(...args: any[]): void } } = {}) {
25
+ export type RelayServer = {
26
+ close(): void
27
+ on<K extends keyof RelayServerEvents>(event: K, listener: RelayServerEvents[K]): void
28
+ off<K extends keyof RelayServerEvents>(event: K, listener: RelayServerEvents[K]): void
29
+ }
30
+
31
+ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.0.0.1', logger }: { port?: number; host?: string; logger?: { log(...args: any[]): void; error(...args: any[]): void } } = {}): Promise<RelayServer> {
32
+ const emitter = new EventEmitter()
25
33
  const connectedTargets = new Map<string, ConnectedTarget>()
26
34
 
27
35
  const playwrightClients = new Map<string, PlaywrightClient>()
@@ -314,6 +322,8 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
314
322
  id
315
323
  })
316
324
 
325
+ emitter.emit('cdp:command', { clientId, command: message })
326
+
317
327
  if (!extensionWs) {
318
328
  sendToPlaywright({
319
329
  message: {
@@ -395,20 +405,18 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
395
405
  }
396
406
  }
397
407
 
398
- sendToPlaywright({
399
- message: { id, sessionId, result },
400
- clientId
401
- })
408
+ const response: CDPResponseBase = { id, sessionId, result }
409
+ sendToPlaywright({ message: response, clientId })
410
+ emitter.emit('cdp:response', { clientId, response, command: message })
402
411
  } catch (e) {
403
412
  logger?.error('Error handling CDP command:', method, params, e)
404
- sendToPlaywright({
405
- message: {
406
- id,
407
- sessionId,
408
- error: { message: (e as Error).message }
409
- },
410
- clientId
411
- })
413
+ const errorResponse: CDPResponseBase = {
414
+ id,
415
+ sessionId,
416
+ error: { message: (e as Error).message }
417
+ }
418
+ sendToPlaywright({ message: errorResponse, clientId })
419
+ emitter.emit('cdp:response', { clientId, response: errorResponse, command: message })
412
420
  }
413
421
  },
414
422
 
@@ -492,6 +500,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
492
500
  params
493
501
  })
494
502
 
503
+ const cdpEvent: CDPEventBase = { method, sessionId, params }
504
+ emitter.emit('cdp:event', { event: cdpEvent, sessionId })
505
+
495
506
  if (method === 'Target.attachedToTarget') {
496
507
  const targetParams = params as Protocol.Target.AttachedToTargetEvent
497
508
 
@@ -608,6 +619,13 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
608
619
  playwrightClients.clear()
609
620
  extensionWs?.close(1000, 'Server stopped')
610
621
  server.close()
622
+ emitter.removeAllListeners()
623
+ },
624
+ on<K extends keyof RelayServerEvents>(event: K, listener: RelayServerEvents[K]) {
625
+ emitter.on(event, listener as (...args: unknown[]) => void)
626
+ },
627
+ off<K extends keyof RelayServerEvents>(event: K, listener: RelayServerEvents[K]) {
628
+ emitter.off(event, listener as (...args: unknown[]) => void)
611
629
  }
612
630
  }
613
631
  }