playwriter 0.0.63 → 0.0.89

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 (223) hide show
  1. package/dist/a11y-client.js +18 -8
  2. package/dist/aria-snapshot.d.ts +41 -3
  3. package/dist/aria-snapshot.d.ts.map +1 -1
  4. package/dist/aria-snapshot.js +134 -55
  5. package/dist/aria-snapshot.js.map +1 -1
  6. package/dist/aria-snapshot.test.js +5 -2
  7. package/dist/aria-snapshot.test.js.map +1 -1
  8. package/dist/aria-snapshot.unit.test.js +83 -41
  9. package/dist/aria-snapshot.unit.test.js.map +1 -1
  10. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  11. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  12. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  13. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  14. package/dist/bippy.js +1 -1
  15. package/dist/cdp-log.d.ts +1 -1
  16. package/dist/cdp-log.d.ts.map +1 -1
  17. package/dist/cdp-log.js +1 -1
  18. package/dist/cdp-log.js.map +1 -1
  19. package/dist/cdp-relay.d.ts.map +1 -1
  20. package/dist/cdp-relay.js +492 -298
  21. package/dist/cdp-relay.js.map +1 -1
  22. package/dist/cdp-session.d.ts.map +1 -1
  23. package/dist/cdp-session.js.map +1 -1
  24. package/dist/cdp-types.d.ts.map +1 -1
  25. package/dist/cdp-types.js +7 -7
  26. package/dist/cdp-types.js.map +1 -1
  27. package/dist/clean-html.d.ts.map +1 -1
  28. package/dist/clean-html.js +4 -5
  29. package/dist/clean-html.js.map +1 -1
  30. package/dist/cli.js +45 -27
  31. package/dist/cli.js.map +1 -1
  32. package/dist/create-logger.d.ts.map +1 -1
  33. package/dist/create-logger.js +3 -1
  34. package/dist/create-logger.js.map +1 -1
  35. package/dist/debugger-examples-types.d.ts.map +1 -1
  36. package/dist/debugger.d.ts.map +1 -1
  37. package/dist/debugger.js +1 -3
  38. package/dist/debugger.js.map +1 -1
  39. package/dist/diff-utils.d.ts.map +1 -1
  40. package/dist/diff-utils.js +1 -4
  41. package/dist/diff-utils.js.map +1 -1
  42. package/dist/editor-api.md +12 -2
  43. package/dist/editor-examples.d.ts +1 -1
  44. package/dist/editor-examples.d.ts.map +1 -1
  45. package/dist/editor-examples.js +1 -1
  46. package/dist/editor-examples.js.map +1 -1
  47. package/dist/editor.d.ts +1 -1
  48. package/dist/editor.d.ts.map +1 -1
  49. package/dist/editor.js +1 -1
  50. package/dist/editor.js.map +1 -1
  51. package/dist/executor.d.ts +26 -3
  52. package/dist/executor.d.ts.map +1 -1
  53. package/dist/executor.js +297 -64
  54. package/dist/executor.js.map +1 -1
  55. package/dist/executor.unit.test.js +38 -1
  56. package/dist/executor.unit.test.js.map +1 -1
  57. package/dist/extension-connection.test.js +139 -36
  58. package/dist/extension-connection.test.js.map +1 -1
  59. package/dist/ffmpeg.d.ts +148 -0
  60. package/dist/ffmpeg.d.ts.map +1 -0
  61. package/dist/ffmpeg.js +523 -0
  62. package/dist/ffmpeg.js.map +1 -0
  63. package/dist/ghost-browser.d.ts.map +1 -1
  64. package/dist/ghost-browser.js.map +1 -1
  65. package/dist/ghost-cursor-client.js +287 -0
  66. package/dist/ghost-cursor.d.ts +27 -0
  67. package/dist/ghost-cursor.d.ts.map +1 -0
  68. package/dist/ghost-cursor.js +63 -0
  69. package/dist/ghost-cursor.js.map +1 -0
  70. package/dist/htmlrewrite.d.ts.map +1 -1
  71. package/dist/htmlrewrite.js +17 -55
  72. package/dist/htmlrewrite.js.map +1 -1
  73. package/dist/htmlrewrite.test.js.map +1 -1
  74. package/dist/kill-port.d.ts.map +1 -1
  75. package/dist/kill-port.js +1 -3
  76. package/dist/kill-port.js.map +1 -1
  77. package/dist/locator-selector.test.d.ts +2 -0
  78. package/dist/locator-selector.test.d.ts.map +1 -0
  79. package/dist/locator-selector.test.js +96 -0
  80. package/dist/locator-selector.test.js.map +1 -0
  81. package/dist/mcp-client.js.map +1 -1
  82. package/dist/mcp.d.ts.map +1 -1
  83. package/dist/mcp.js +8 -3
  84. package/dist/mcp.js.map +1 -1
  85. package/dist/on-mouse-action.test.d.ts +2 -0
  86. package/dist/on-mouse-action.test.d.ts.map +1 -0
  87. package/dist/on-mouse-action.test.js +155 -0
  88. package/dist/on-mouse-action.test.js.map +1 -0
  89. package/dist/page-markdown.js +4 -4
  90. package/dist/page-markdown.js.map +1 -1
  91. package/dist/prompt.md +450 -377
  92. package/dist/protocol.d.ts +4 -0
  93. package/dist/protocol.d.ts.map +1 -1
  94. package/dist/readability.js +16 -2
  95. package/dist/recording-ghost-cursor.d.ts +41 -0
  96. package/dist/recording-ghost-cursor.d.ts.map +1 -0
  97. package/dist/recording-ghost-cursor.js +79 -0
  98. package/dist/recording-ghost-cursor.js.map +1 -0
  99. package/dist/recording-relay.d.ts.map +1 -1
  100. package/dist/recording-relay.js +8 -8
  101. package/dist/recording-relay.js.map +1 -1
  102. package/dist/relay-client.d.ts +17 -4
  103. package/dist/relay-client.d.ts.map +1 -1
  104. package/dist/relay-client.js +45 -11
  105. package/dist/relay-client.js.map +1 -1
  106. package/dist/relay-core.test.d.ts.map +1 -1
  107. package/dist/relay-core.test.js +515 -26
  108. package/dist/relay-core.test.js.map +1 -1
  109. package/dist/relay-navigation.test.d.ts.map +1 -1
  110. package/dist/relay-navigation.test.js +169 -31
  111. package/dist/relay-navigation.test.js.map +1 -1
  112. package/dist/relay-session.test.d.ts.map +1 -1
  113. package/dist/relay-session.test.js +113 -65
  114. package/dist/relay-session.test.js.map +1 -1
  115. package/dist/relay-state.d.ts +158 -0
  116. package/dist/relay-state.d.ts.map +1 -0
  117. package/dist/relay-state.js +306 -0
  118. package/dist/relay-state.js.map +1 -0
  119. package/dist/relay-state.test.d.ts +2 -0
  120. package/dist/relay-state.test.d.ts.map +1 -0
  121. package/dist/relay-state.test.js +472 -0
  122. package/dist/relay-state.test.js.map +1 -0
  123. package/dist/scoped-fs.d.ts.map +1 -1
  124. package/dist/scoped-fs.js.map +1 -1
  125. package/dist/screen-recording.d.ts +66 -4
  126. package/dist/screen-recording.d.ts.map +1 -1
  127. package/dist/screen-recording.js +150 -13
  128. package/dist/screen-recording.js.map +1 -1
  129. package/dist/screen-recording.test.d.ts +2 -0
  130. package/dist/screen-recording.test.d.ts.map +1 -0
  131. package/dist/screen-recording.test.js +102 -0
  132. package/dist/screen-recording.test.js.map +1 -0
  133. package/dist/selector-generator.js +1 -1
  134. package/dist/snapshot-tools.test.js +71 -28
  135. package/dist/snapshot-tools.test.js.map +1 -1
  136. package/dist/start-relay-server.d.ts +1 -1
  137. package/dist/start-relay-server.d.ts.map +1 -1
  138. package/dist/start-relay-server.js +1 -1
  139. package/dist/start-relay-server.js.map +1 -1
  140. package/dist/styles-api.md +8 -1
  141. package/dist/styles-examples.d.ts +1 -1
  142. package/dist/styles-examples.d.ts.map +1 -1
  143. package/dist/styles-examples.js +1 -1
  144. package/dist/styles-examples.js.map +1 -1
  145. package/dist/styles.d.ts.map +1 -1
  146. package/dist/styles.js +1 -3
  147. package/dist/styles.js.map +1 -1
  148. package/dist/test-declarations.d.ts.map +1 -1
  149. package/dist/test-utils.d.ts +1 -1
  150. package/dist/test-utils.d.ts.map +1 -1
  151. package/dist/test-utils.js +7 -5
  152. package/dist/test-utils.js.map +1 -1
  153. package/dist/utils.d.ts.map +1 -1
  154. package/dist/utils.js.map +1 -1
  155. package/dist/wait-for-page-load.d.ts.map +1 -1
  156. package/dist/wait-for-page-load.js +1 -1
  157. package/dist/wait-for-page-load.js.map +1 -1
  158. package/package.json +4 -3
  159. package/src/a11y-client.ts +5 -4
  160. package/src/aria-snapshot.test.ts +5 -2
  161. package/src/aria-snapshot.ts +306 -117
  162. package/src/aria-snapshot.unit.test.ts +199 -141
  163. package/src/aria-snapshots/github-interactive.txt +2 -0
  164. package/src/aria-snapshots/github-raw.txt +5 -1
  165. package/src/aria-snapshots/hackernews-interactive.txt +238 -241
  166. package/src/aria-snapshots/hackernews-raw.txt +265 -269
  167. package/src/assets/aria-labels-example.png +0 -0
  168. package/src/assets/aria-labels-github.png +0 -0
  169. package/src/assets/aria-labels-hacker-news.png +0 -0
  170. package/src/assets/aria-labels-old-reddit.png +0 -0
  171. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  172. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  173. package/src/cdp-log.ts +4 -1
  174. package/src/cdp-relay.ts +1059 -737
  175. package/src/cdp-session.ts +12 -3
  176. package/src/cdp-types.ts +51 -51
  177. package/src/clean-html.ts +4 -5
  178. package/src/cli.ts +82 -55
  179. package/src/create-logger.ts +5 -3
  180. package/src/debugger-examples-types.ts +4 -1
  181. package/src/debugger.ts +1 -5
  182. package/src/diff-utils.ts +2 -5
  183. package/src/editor-examples.ts +11 -1
  184. package/src/editor.ts +10 -2
  185. package/src/executor.ts +374 -73
  186. package/src/executor.unit.test.ts +48 -1
  187. package/src/extension-connection.test.ts +612 -488
  188. package/src/ffmpeg.ts +769 -0
  189. package/src/ghost-browser.ts +4 -6
  190. package/src/ghost-cursor-client.ts +369 -0
  191. package/src/ghost-cursor.ts +110 -0
  192. package/src/htmlrewrite.test.ts +6 -2
  193. package/src/htmlrewrite.ts +348 -386
  194. package/src/kill-port.ts +1 -3
  195. package/src/locator-selector.test.ts +115 -0
  196. package/src/mcp-client.ts +1 -1
  197. package/src/mcp.ts +21 -15
  198. package/src/on-mouse-action.test.ts +196 -0
  199. package/src/page-markdown.ts +7 -7
  200. package/src/protocol.ts +73 -57
  201. package/src/recording-ghost-cursor.ts +113 -0
  202. package/src/recording-relay.ts +20 -12
  203. package/src/relay-client.ts +85 -18
  204. package/src/relay-core.test.ts +1117 -578
  205. package/src/relay-navigation.test.ts +648 -483
  206. package/src/relay-session.test.ts +984 -929
  207. package/src/relay-state.test.ts +570 -0
  208. package/src/relay-state.ts +497 -0
  209. package/src/resource.md +21 -49
  210. package/src/scoped-fs.ts +9 -3
  211. package/src/screen-recording.test.ts +111 -0
  212. package/src/screen-recording.ts +256 -31
  213. package/src/skill.md +476 -396
  214. package/src/snapshot-tools.test.ts +580 -528
  215. package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
  216. package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
  217. package/src/start-relay-server.ts +14 -11
  218. package/src/styles-examples.ts +8 -1
  219. package/src/styles.ts +20 -21
  220. package/src/test-declarations.ts +6 -6
  221. package/src/test-utils.ts +104 -91
  222. package/src/utils.ts +2 -1
  223. package/src/wait-for-page-load.ts +6 -1
@@ -6,7 +6,16 @@ import { getCDPSessionForPage } from './cdp-session.js'
6
6
  import { Debugger } from './debugger.js'
7
7
  import { Editor } from './editor.js'
8
8
  import { PlaywrightExecutor } from './executor.js'
9
- import { setupTestContext, cleanupTestContext, getExtensionServiceWorker, createSseServer, safeCloseCDPBrowser, type TestContext, withTimeout, js } from './test-utils.js'
9
+ import {
10
+ setupTestContext,
11
+ cleanupTestContext,
12
+ getExtensionServiceWorker,
13
+ createSseServer,
14
+ safeCloseCDPBrowser,
15
+ type TestContext,
16
+ withTimeout,
17
+ js,
18
+ } from './test-utils.js'
10
19
  import './test-declarations.js'
11
20
 
12
21
  const TEST_PORT = 19993
@@ -14,121 +23,119 @@ const TEST_PORT = 19993
14
23
  // --- CDP Session Tests ---
15
24
 
16
25
  describe('CDP Session Tests', () => {
17
- let testCtx: TestContext | null = null
26
+ let testCtx: TestContext | null = null
18
27
 
19
- beforeAll(async () => {
20
- testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-cdp-test-', toggleExtension: true })
28
+ beforeAll(async () => {
29
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-cdp-test-', toggleExtension: true })
21
30
 
22
- const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
23
- await serviceWorker.evaluate(async () => {
24
- await globalThis.disconnectEverything()
25
- })
26
- await new Promise(r => setTimeout(r, 100))
27
- }, 600000)
28
-
29
- afterAll(async () => {
30
- await cleanupTestContext(testCtx)
31
- testCtx = null
31
+ const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
32
+ await serviceWorker.evaluate(async () => {
33
+ await globalThis.disconnectEverything()
32
34
  })
35
+ await new Promise((r) => setTimeout(r, 100))
36
+ }, 600000)
37
+
38
+ afterAll(async () => {
39
+ await cleanupTestContext(testCtx)
40
+ testCtx = null
41
+ })
42
+
43
+ const getBrowserContext = () => {
44
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
45
+ return testCtx.browserContext
46
+ }
47
+
48
+ it('should use Debugger class to set breakpoints and inspect variables', async () => {
49
+ const browserContext = getBrowserContext()
50
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
51
+
52
+ const page = await browserContext.newPage()
53
+ const testUrl = 'https://example.com/?test=debugger-variables'
54
+ await page.goto(testUrl)
55
+ await page.bringToFront()
56
+
57
+ await serviceWorker.evaluate(async () => {
58
+ await globalThis.toggleExtensionForActiveTab()
59
+ })
60
+ await new Promise((r) => setTimeout(r, 100))
33
61
 
34
- const getBrowserContext = () => {
35
- if (!testCtx?.browserContext) throw new Error('Browser not initialized')
36
- return testCtx.browserContext
37
- }
38
-
39
-
40
-
41
- it('should use Debugger class to set breakpoints and inspect variables', async () => {
42
- const browserContext = getBrowserContext()
43
- const serviceWorker = await getExtensionServiceWorker(browserContext)
44
-
45
- const page = await browserContext.newPage()
46
- const testUrl = 'https://example.com/?test=debugger-variables'
47
- await page.goto(testUrl)
48
- await page.bringToFront()
49
-
50
- await serviceWorker.evaluate(async () => {
51
- await globalThis.toggleExtensionForActiveTab()
52
- })
53
- await new Promise(r => setTimeout(r, 100))
54
-
55
- const wsUrl = getCdpUrl({ port: TEST_PORT })
56
- const cdpSession = await getCDPSessionForPage({ page })
57
- const dbg = new Debugger({ cdp: cdpSession })
62
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
63
+ const cdpSession = await getCDPSessionForPage({ page })
64
+ const dbg = new Debugger({ cdp: cdpSession })
58
65
 
59
- await dbg.enable()
66
+ await dbg.enable()
60
67
 
61
- expect(dbg.isPaused()).toBe(false)
68
+ expect(dbg.isPaused()).toBe(false)
62
69
 
63
- const pausedPromise = new Promise<void>((resolve) => {
64
- cdpSession.on('Debugger.paused', () => {
65
- resolve()
66
- })
67
- })
70
+ const pausedPromise = new Promise<void>((resolve) => {
71
+ cdpSession.on('Debugger.paused', () => {
72
+ resolve()
73
+ })
74
+ })
68
75
 
69
- const evalPromise = cdpSession.send('Runtime.evaluate', {
70
- expression: `(function testFunction() {
76
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
77
+ expression: `(function testFunction() {
71
78
  const localVar = 'hello';
72
79
  const numberVar = 42;
73
80
  debugger;
74
81
  return localVar + numberVar;
75
- })()`
76
- })
82
+ })()`,
83
+ })
77
84
 
78
- await Promise.race([
79
- pausedPromise,
80
- new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
81
- ])
85
+ await Promise.race([
86
+ pausedPromise,
87
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
88
+ ])
82
89
 
83
- expect(dbg.isPaused()).toBe(true)
90
+ expect(dbg.isPaused()).toBe(true)
84
91
 
85
- const location = await dbg.getLocation()
86
- expect(location.callstack[0].functionName).toBe('testFunction')
87
- expect(location.sourceContext).toContain('debugger')
92
+ const location = await dbg.getLocation()
93
+ expect(location.callstack[0].functionName).toBe('testFunction')
94
+ expect(location.sourceContext).toContain('debugger')
88
95
 
89
- const vars = await dbg.inspectLocalVariables()
90
- expect(vars).toMatchInlineSnapshot(`
96
+ const vars = await dbg.inspectLocalVariables()
97
+ expect(vars).toMatchInlineSnapshot(`
91
98
  {
92
99
  "localVar": "hello",
93
100
  "numberVar": 42,
94
101
  }
95
102
  `)
96
103
 
97
- const evalResult = await dbg.evaluate({ expression: 'localVar + " world"' })
98
- expect(evalResult.value).toBe('hello world')
99
-
100
- await dbg.resume()
101
- await new Promise(r => setTimeout(r, 100))
102
- expect(dbg.isPaused()).toBe(false)
103
- await evalPromise
104
+ const evalResult = await dbg.evaluate({ expression: 'localVar + " world"' })
105
+ expect(evalResult.value).toBe('hello world')
104
106
 
105
- await cdpSession.detach()
106
- await page.close()
107
- }, 60000)
107
+ await dbg.resume()
108
+ await new Promise((r) => setTimeout(r, 100))
109
+ expect(dbg.isPaused()).toBe(false)
110
+ await evalPromise
108
111
 
109
- it('should reuse cached CDP session and close on page close', async () => {
110
- const browserContext = getBrowserContext()
111
- const serviceWorker = await getExtensionServiceWorker(browserContext)
112
+ await cdpSession.detach()
113
+ await page.close()
114
+ }, 60000)
112
115
 
113
- const page = await browserContext.newPage()
114
- const testUrl = 'https://example.com/?test=debugger-step'
115
- await page.goto(testUrl)
116
- await page.bringToFront()
116
+ it('should reuse cached CDP session and close on page close', async () => {
117
+ const browserContext = getBrowserContext()
118
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
117
119
 
118
- await serviceWorker.evaluate(async () => {
119
- await globalThis.toggleExtensionForActiveTab()
120
- })
121
- await new Promise(r => setTimeout(r, 100))
120
+ const page = await browserContext.newPage()
121
+ const testUrl = 'https://example.com/?test=debugger-step'
122
+ await page.goto(testUrl)
123
+ await page.bringToFront()
122
124
 
123
- const executor = new PlaywrightExecutor({
124
- cdpConfig: { port: TEST_PORT },
125
- logger: {
126
- log: () => {},
127
- error: () => {},
128
- },
129
- })
125
+ await serviceWorker.evaluate(async () => {
126
+ await globalThis.toggleExtensionForActiveTab()
127
+ })
128
+ await new Promise((r) => setTimeout(r, 100))
129
+
130
+ const executor = new PlaywrightExecutor({
131
+ cdpConfig: { port: TEST_PORT },
132
+ logger: {
133
+ log: () => {},
134
+ error: () => {},
135
+ },
136
+ })
130
137
 
131
- const result = await executor.execute(js`
138
+ const result = await executor.execute(js`
132
139
  const sessionA = await getCDPSession({ page })
133
140
  const sessionB = await getCDPSession({ page })
134
141
  await sessionA.send('Runtime.evaluate', { expression: '1 + 1', returnByValue: true })
@@ -136,52 +143,63 @@ describe('CDP Session Tests', () => {
136
143
  return evalResult.result.value
137
144
  `)
138
145
 
139
- expect(result.isError).toBe(false)
140
- expect(result.text).toContain('[return value] 4')
141
-
142
- await page.close()
143
- }, 60000)
144
-
145
- it('should list scripts with Debugger class', async () => {
146
- const browserContext = getBrowserContext()
147
- const serviceWorker = await getExtensionServiceWorker(browserContext)
148
-
149
- // Use a page with actual script URLs (not just inline scripts)
150
- const page = await browserContext.newPage()
151
- await page.goto('https://news.ycombinator.com/')
152
- await page.bringToFront()
153
-
154
- await serviceWorker.evaluate(async () => {
155
- await globalThis.toggleExtensionForActiveTab()
156
- })
157
- await new Promise(r => setTimeout(r, 100))
146
+ expect(result.isError).toBe(false)
147
+ expect(result.text).toContain('[return value] 4')
148
+
149
+ await page.close()
150
+ }, 60000)
151
+
152
+ it('should list scripts with Debugger class', async () => {
153
+ const browserContext = getBrowserContext()
154
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
155
+
156
+ // Use setContent with external script URLs so Debugger.listScripts returns them
157
+ const page = await browserContext.newPage()
158
+ await page.setContent(`
159
+ <html>
160
+ <head>
161
+ <script src="data:text/javascript,function hello() { return 1; }"></script>
162
+ <script src="data:text/javascript,function world() { return 2; }"></script>
163
+ </head>
164
+ <body><h1>Script test</h1></body>
165
+ </html>
166
+ `)
167
+ await page.bringToFront()
168
+
169
+ await serviceWorker.evaluate(async () => {
170
+ await globalThis.toggleExtensionForActiveTab()
171
+ })
172
+ await new Promise((r) => setTimeout(r, 100))
158
173
 
159
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
160
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('news.ycombinator'))
161
- expect(cdpPage).toBeDefined()
174
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
175
+ const cdpPage = browser
176
+ .contexts()[0]
177
+ .pages()
178
+ .find((p) => p.url().startsWith('about:'))
179
+ expect(cdpPage).toBeDefined()
162
180
 
163
- const wsUrl = getCdpUrl({ port: TEST_PORT })
164
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
165
- const dbg = new Debugger({ cdp: cdpSession })
181
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
182
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
183
+ const dbg = new Debugger({ cdp: cdpSession })
166
184
 
167
- await dbg.enable()
185
+ await dbg.enable()
168
186
 
169
- const scripts = await dbg.listScripts()
170
- expect(scripts.length).toBeGreaterThan(0)
171
- expect(scripts[0]).toHaveProperty('scriptId')
172
- expect(scripts[0]).toHaveProperty('url')
187
+ const scripts = await dbg.listScripts()
188
+ expect(scripts.length).toBeGreaterThan(0)
189
+ expect(scripts[0]).toHaveProperty('scriptId')
190
+ expect(scripts[0]).toHaveProperty('url')
173
191
 
174
- await cdpSession.detach()
175
- await browser.close()
176
- await page.close()
177
- }, 60000)
192
+ await cdpSession.detach()
193
+ await browser.close()
194
+ await page.close()
195
+ }, 60000)
178
196
 
179
- it('should manage breakpoints with Debugger class', async () => {
180
- const browserContext = getBrowserContext()
181
- const serviceWorker = await getExtensionServiceWorker(browserContext)
197
+ it('should manage breakpoints with Debugger class', async () => {
198
+ const browserContext = getBrowserContext()
199
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
182
200
 
183
- const page = await browserContext.newPage()
184
- await page.setContent(`
201
+ const page = await browserContext.newPage()
202
+ await page.setContent(`
185
203
  <html>
186
204
  <head>
187
205
  <script src="data:text/javascript,function testFunc() { return 42; }"></script>
@@ -189,62 +207,62 @@ describe('CDP Session Tests', () => {
189
207
  <body></body>
190
208
  </html>
191
209
  `)
192
- await page.bringToFront()
210
+ await page.bringToFront()
193
211
 
194
- await serviceWorker.evaluate(async () => {
195
- await globalThis.toggleExtensionForActiveTab()
196
- })
197
- await new Promise(r => setTimeout(r, 100))
212
+ await serviceWorker.evaluate(async () => {
213
+ await globalThis.toggleExtensionForActiveTab()
214
+ })
215
+ await new Promise((r) => setTimeout(r, 100))
198
216
 
199
- const wsUrl = getCdpUrl({ port: TEST_PORT })
200
- const cdpSession = await getCDPSessionForPage({ page })
201
- const dbg = new Debugger({ cdp: cdpSession })
217
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
218
+ const cdpSession = await getCDPSessionForPage({ page })
219
+ const dbg = new Debugger({ cdp: cdpSession })
202
220
 
203
- await dbg.enable()
221
+ await dbg.enable()
204
222
 
205
- expect(dbg.listBreakpoints()).toHaveLength(0)
223
+ expect(dbg.listBreakpoints()).toHaveLength(0)
206
224
 
207
- const bpId = await dbg.setBreakpoint({ file: 'https://example.com/test.js', line: 1 })
208
- expect(typeof bpId).toBe('string')
209
- expect(dbg.listBreakpoints()).toHaveLength(1)
210
- expect(dbg.listBreakpoints()[0]).toMatchObject({
211
- id: bpId,
212
- file: 'https://example.com/test.js',
213
- line: 1,
214
- })
225
+ const bpId = await dbg.setBreakpoint({ file: 'https://example.com/test.js', line: 1 })
226
+ expect(typeof bpId).toBe('string')
227
+ expect(dbg.listBreakpoints()).toHaveLength(1)
228
+ expect(dbg.listBreakpoints()[0]).toMatchObject({
229
+ id: bpId,
230
+ file: 'https://example.com/test.js',
231
+ line: 1,
232
+ })
215
233
 
216
- await dbg.deleteBreakpoint({ breakpointId: bpId })
217
- expect(dbg.listBreakpoints()).toHaveLength(0)
234
+ await dbg.deleteBreakpoint({ breakpointId: bpId })
235
+ expect(dbg.listBreakpoints()).toHaveLength(0)
218
236
 
219
- await cdpSession.detach()
220
- await page.close()
221
- }, 60000)
237
+ await cdpSession.detach()
238
+ await page.close()
239
+ }, 60000)
222
240
 
223
- it('should step through code with Debugger class', async () => {
224
- const browserContext = getBrowserContext()
225
- const serviceWorker = await getExtensionServiceWorker(browserContext)
241
+ it('should step through code with Debugger class', async () => {
242
+ const browserContext = getBrowserContext()
243
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
226
244
 
227
- const page = await browserContext.newPage()
228
- await page.goto('https://example.com/')
229
- await page.bringToFront()
245
+ const page = await browserContext.newPage()
246
+ await page.goto('https://example.com/')
247
+ await page.bringToFront()
230
248
 
231
- await serviceWorker.evaluate(async () => {
232
- await globalThis.toggleExtensionForActiveTab()
233
- })
234
- await new Promise(r => setTimeout(r, 100))
249
+ await serviceWorker.evaluate(async () => {
250
+ await globalThis.toggleExtensionForActiveTab()
251
+ })
252
+ await new Promise((r) => setTimeout(r, 100))
235
253
 
236
- const wsUrl = getCdpUrl({ port: TEST_PORT })
237
- const cdpSession = await getCDPSessionForPage({ page })
238
- const dbg = new Debugger({ cdp: cdpSession })
254
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
255
+ const cdpSession = await getCDPSessionForPage({ page })
256
+ const dbg = new Debugger({ cdp: cdpSession })
239
257
 
240
- await dbg.enable()
258
+ await dbg.enable()
241
259
 
242
- const pausedPromise = new Promise<void>((resolve) => {
243
- cdpSession.on('Debugger.paused', () => resolve())
244
- })
260
+ const pausedPromise = new Promise<void>((resolve) => {
261
+ cdpSession.on('Debugger.paused', () => resolve())
262
+ })
245
263
 
246
- const evalPromise = cdpSession.send('Runtime.evaluate', {
247
- expression: `(function outer() {
264
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
265
+ expression: `(function outer() {
248
266
  function inner() {
249
267
  const x = 1;
250
268
  debugger;
@@ -253,63 +271,63 @@ describe('CDP Session Tests', () => {
253
271
  }
254
272
  const result = inner();
255
273
  return result;
256
- })()`
257
- })
274
+ })()`,
275
+ })
258
276
 
259
- await pausedPromise
260
- expect(dbg.isPaused()).toBe(true)
277
+ await pausedPromise
278
+ expect(dbg.isPaused()).toBe(true)
261
279
 
262
- const location1 = await dbg.getLocation()
263
- expect(location1.callstack.length).toBeGreaterThanOrEqual(2)
264
- expect(location1.callstack[0].functionName).toBe('inner')
265
- expect(location1.callstack[1].functionName).toBe('outer')
280
+ const location1 = await dbg.getLocation()
281
+ expect(location1.callstack.length).toBeGreaterThanOrEqual(2)
282
+ expect(location1.callstack[0].functionName).toBe('inner')
283
+ expect(location1.callstack[1].functionName).toBe('outer')
266
284
 
267
- const stepOverPromise = new Promise<void>((resolve) => {
268
- cdpSession.on('Debugger.paused', () => resolve())
269
- })
270
- await dbg.stepOver()
271
- await stepOverPromise
285
+ const stepOverPromise = new Promise<void>((resolve) => {
286
+ cdpSession.on('Debugger.paused', () => resolve())
287
+ })
288
+ await dbg.stepOver()
289
+ await stepOverPromise
272
290
 
273
- const location2 = await dbg.getLocation()
274
- expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber)
291
+ const location2 = await dbg.getLocation()
292
+ expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber)
275
293
 
276
- const stepOutPromise = new Promise<void>((resolve) => {
277
- cdpSession.on('Debugger.paused', () => resolve())
278
- })
279
- await dbg.stepOut()
280
- await stepOutPromise
294
+ const stepOutPromise = new Promise<void>((resolve) => {
295
+ cdpSession.on('Debugger.paused', () => resolve())
296
+ })
297
+ await dbg.stepOut()
298
+ await stepOutPromise
281
299
 
282
- const location3 = await dbg.getLocation()
283
- expect(location3.callstack[0].functionName).toBe('outer')
300
+ const location3 = await dbg.getLocation()
301
+ expect(location3.callstack[0].functionName).toBe('outer')
284
302
 
285
- await dbg.resume()
286
- await evalPromise
303
+ await dbg.resume()
304
+ await evalPromise
287
305
 
288
- await cdpSession.detach()
289
- await page.close()
290
- }, 60000)
306
+ await cdpSession.detach()
307
+ await page.close()
308
+ }, 60000)
291
309
 
292
- it('should profile JavaScript execution using CDP Profiler', async () => {
293
- const browserContext = getBrowserContext()
294
- const serviceWorker = await getExtensionServiceWorker(browserContext)
310
+ it('should profile JavaScript execution using CDP Profiler', async () => {
311
+ const browserContext = getBrowserContext()
312
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
295
313
 
296
- const page = await browserContext.newPage()
297
- const testUrl = 'https://example.com/?test=debugger-profiler'
298
- await page.goto(testUrl)
299
- await page.bringToFront()
314
+ const page = await browserContext.newPage()
315
+ const testUrl = 'https://example.com/?test=debugger-profiler'
316
+ await page.goto(testUrl)
317
+ await page.bringToFront()
300
318
 
301
- await serviceWorker.evaluate(async () => {
302
- await globalThis.toggleExtensionForActiveTab()
303
- })
304
- await new Promise(r => setTimeout(r, 100))
319
+ await serviceWorker.evaluate(async () => {
320
+ await globalThis.toggleExtensionForActiveTab()
321
+ })
322
+ await new Promise((r) => setTimeout(r, 100))
305
323
 
306
- const wsUrl = getCdpUrl({ port: TEST_PORT })
307
- const cdpSession = await getCDPSessionForPage({ page })
308
- await cdpSession.send('Profiler.enable')
309
- await cdpSession.send('Profiler.start')
324
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
325
+ const cdpSession = await getCDPSessionForPage({ page })
326
+ await cdpSession.send('Profiler.enable')
327
+ await cdpSession.send('Profiler.start')
310
328
 
311
- await cdpSession.send('Runtime.evaluate', {
312
- expression: `(() => {
329
+ await cdpSession.send('Runtime.evaluate', {
330
+ expression: `(() => {
313
331
  function fibonacci(n) {
314
332
  if (n <= 1) return n
315
333
  return fibonacci(n - 1) + fibonacci(n - 2)
@@ -320,116 +338,122 @@ describe('CDP Session Tests', () => {
320
338
  for (let i = 0; i < 1000; i++) {
321
339
  document.querySelectorAll('*')
322
340
  }
323
- })()`
324
- })
341
+ })()`,
342
+ })
325
343
 
326
- const stopResult = await cdpSession.send('Profiler.stop')
327
- const profile = stopResult.profile
344
+ const stopResult = await cdpSession.send('Profiler.stop')
345
+ const profile = stopResult.profile
328
346
 
329
- const functionNames = profile.nodes
330
- .map(n => n.callFrame.functionName)
331
- .filter(name => name && name.length > 0)
332
- .slice(0, 10)
347
+ const functionNames = profile.nodes
348
+ .map((n) => n.callFrame.functionName)
349
+ .filter((name) => name && name.length > 0)
350
+ .slice(0, 10)
333
351
 
334
- expect(profile.nodes.length).toBeGreaterThan(0)
335
- expect(profile.endTime - profile.startTime).toBeGreaterThan(0)
336
- expect(functionNames.every((name) => typeof name === 'string')).toBe(true)
352
+ expect(profile.nodes.length).toBeGreaterThan(0)
353
+ expect(profile.endTime - profile.startTime).toBeGreaterThan(0)
354
+ expect(functionNames.every((name) => typeof name === 'string')).toBe(true)
337
355
 
338
- await cdpSession.send('Profiler.disable')
339
- await cdpSession.detach()
340
- await page.close()
341
- }, 60000)
356
+ await cdpSession.send('Profiler.disable')
357
+ await cdpSession.detach()
358
+ await page.close()
359
+ }, 60000)
342
360
 
343
- it('should update Target.getTargets URL after page navigation', async () => {
344
- const browserContext = getBrowserContext()
345
- const serviceWorker = await getExtensionServiceWorker(browserContext)
361
+ it('should update Target.getTargets URL after page navigation', async () => {
362
+ const browserContext = getBrowserContext()
363
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
346
364
 
347
- await serviceWorker.evaluate(async () => {
348
- await globalThis.disconnectEverything()
349
- })
365
+ await serviceWorker.evaluate(async () => {
366
+ await globalThis.disconnectEverything()
367
+ })
350
368
 
351
- const page = await browserContext.newPage()
352
- await page.goto('https://example.com/')
353
- await page.bringToFront()
369
+ const page = await browserContext.newPage()
370
+ await page.goto('https://example.com/')
371
+ await page.bringToFront()
354
372
 
355
- await serviceWorker.evaluate(async () => {
356
- await globalThis.toggleExtensionForActiveTab()
357
- })
358
- await new Promise(r => setTimeout(r, 100))
373
+ await serviceWorker.evaluate(async () => {
374
+ await globalThis.toggleExtensionForActiveTab()
375
+ })
376
+ await new Promise((r) => setTimeout(r, 100))
359
377
 
360
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
361
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
362
- expect(cdpPage).toBeDefined()
378
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
379
+ const cdpPage = browser
380
+ .contexts()[0]
381
+ .pages()
382
+ .find((p) => p.url().includes('example.com'))
383
+ expect(cdpPage).toBeDefined()
363
384
 
364
- const wsUrl = getCdpUrl({ port: TEST_PORT })
365
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
385
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
386
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
366
387
 
367
- const initialTargets = await cdpSession.send('Target.getTargets')
368
- const initialPageTarget = initialTargets.targetInfos.find(t => t.type === 'page' && t.url.includes('example.com'))
369
- expect(initialPageTarget?.url).toBe('https://example.com/')
388
+ const initialTargets = await cdpSession.send('Target.getTargets')
389
+ const initialPageTarget = initialTargets.targetInfos.find((t) => t.type === 'page' && t.url.includes('example.com'))
390
+ expect(initialPageTarget?.url).toBe('https://example.com/')
370
391
 
371
- await cdpPage!.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
372
- await new Promise(r => setTimeout(r, 100))
392
+ await cdpPage!.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
393
+ await new Promise((r) => setTimeout(r, 100))
373
394
 
374
- const afterNavTargets = await cdpSession.send('Target.getTargets')
375
- const allPageTargets = afterNavTargets.targetInfos.filter(t => t.type === 'page')
395
+ const afterNavTargets = await cdpSession.send('Target.getTargets')
396
+ const allPageTargets = afterNavTargets.targetInfos.filter((t) => t.type === 'page')
376
397
 
377
- const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank')
378
- expect(aboutBlankTargets).toHaveLength(0)
398
+ const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
399
+ expect(aboutBlankTargets).toHaveLength(0)
379
400
 
380
- const exampleComTargets = allPageTargets.filter(t => t.url.includes('example.com'))
381
- expect(exampleComTargets).toHaveLength(0)
401
+ const exampleComTargets = allPageTargets.filter((t) => t.url.includes('example.com'))
402
+ expect(exampleComTargets).toHaveLength(0)
382
403
 
383
- const exampleOrgTargets = allPageTargets.filter(t => t.url.includes('example.org'))
384
- expect(exampleOrgTargets).toHaveLength(1)
404
+ const exampleOrgTargets = allPageTargets.filter((t) => t.url.includes('example.org'))
405
+ expect(exampleOrgTargets).toHaveLength(1)
385
406
 
386
- await cdpSession.detach()
387
- await browser.close()
388
- await page.close()
389
- }, 60000)
407
+ await cdpSession.detach()
408
+ await browser.close()
409
+ await page.close()
410
+ }, 60000)
390
411
 
391
- it('should return correct targets for multiple pages via Target.getTargets', async () => {
392
- const browserContext = getBrowserContext()
393
- const serviceWorker = await getExtensionServiceWorker(browserContext)
412
+ it('should return correct targets for multiple pages via Target.getTargets', async () => {
413
+ const browserContext = getBrowserContext()
414
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
394
415
 
395
- await serviceWorker.evaluate(async () => {
396
- await globalThis.disconnectEverything()
397
- })
416
+ await serviceWorker.evaluate(async () => {
417
+ await globalThis.disconnectEverything()
418
+ })
398
419
 
399
- const page1 = await browserContext.newPage()
400
- await page1.goto('https://example.com/')
401
- await page1.bringToFront()
402
- await serviceWorker.evaluate(async () => {
403
- await globalThis.toggleExtensionForActiveTab()
404
- })
420
+ const page1 = await browserContext.newPage()
421
+ await page1.goto('https://example.com/')
422
+ await page1.bringToFront()
423
+ await serviceWorker.evaluate(async () => {
424
+ await globalThis.toggleExtensionForActiveTab()
425
+ })
405
426
 
406
- const page2 = await browserContext.newPage()
407
- await page2.goto('https://example.org/')
408
- await page2.bringToFront()
409
- await serviceWorker.evaluate(async () => {
410
- await globalThis.toggleExtensionForActiveTab()
411
- })
427
+ const page2 = await browserContext.newPage()
428
+ await page2.goto('https://example.org/')
429
+ await page2.bringToFront()
430
+ await serviceWorker.evaluate(async () => {
431
+ await globalThis.toggleExtensionForActiveTab()
432
+ })
412
433
 
413
- await new Promise(r => setTimeout(r, 100))
434
+ await new Promise((r) => setTimeout(r, 100))
414
435
 
415
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
416
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
417
- expect(cdpPage).toBeDefined()
436
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
437
+ const cdpPage = browser
438
+ .contexts()[0]
439
+ .pages()
440
+ .find((p) => p.url().includes('example.com'))
441
+ expect(cdpPage).toBeDefined()
418
442
 
419
- const wsUrl = getCdpUrl({ port: TEST_PORT })
420
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
443
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
444
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
421
445
 
422
- const { targetInfos } = await cdpSession.send('Target.getTargets')
423
- const allPageTargets = targetInfos.filter(t => t.type === 'page')
446
+ const { targetInfos } = await cdpSession.send('Target.getTargets')
447
+ const allPageTargets = targetInfos.filter((t) => t.type === 'page')
424
448
 
425
- const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank')
426
- expect(aboutBlankTargets).toHaveLength(0)
449
+ const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
450
+ expect(aboutBlankTargets).toHaveLength(0)
427
451
 
428
- const pageTargets = allPageTargets
429
- .map(t => ({ type: t.type, url: t.url }))
430
- .sort((a, b) => a.url.localeCompare(b.url))
452
+ const pageTargets = allPageTargets
453
+ .map((t) => ({ type: t.type, url: t.url }))
454
+ .sort((a, b) => a.url.localeCompare(b.url))
431
455
 
432
- expect(pageTargets).toMatchInlineSnapshot(`
456
+ expect(pageTargets).toMatchInlineSnapshot(`
433
457
  [
434
458
  {
435
459
  "type": "page",
@@ -442,163 +466,172 @@ describe('CDP Session Tests', () => {
442
466
  ]
443
467
  `)
444
468
 
445
- await cdpSession.detach()
446
- await browser.close()
447
- await page1.close()
448
- await page2.close()
449
- }, 60000)
469
+ await cdpSession.detach()
470
+ await browser.close()
471
+ await page1.close()
472
+ await page2.close()
473
+ }, 60000)
450
474
 
451
- it('should create CDP session for page after navigation', async () => {
452
- const browserContext = getBrowserContext()
453
- const serviceWorker = await getExtensionServiceWorker(browserContext)
475
+ it('should create CDP session for page after navigation', async () => {
476
+ const browserContext = getBrowserContext()
477
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
454
478
 
455
- const page = await browserContext.newPage()
456
- await page.goto('https://example.com/')
457
- await page.bringToFront()
479
+ const page = await browserContext.newPage()
480
+ await page.goto('https://example.com/')
481
+ await page.bringToFront()
458
482
 
459
- await serviceWorker.evaluate(async () => {
460
- await globalThis.toggleExtensionForActiveTab()
461
- })
462
- await new Promise(r => setTimeout(r, 100))
483
+ await serviceWorker.evaluate(async () => {
484
+ await globalThis.toggleExtensionForActiveTab()
485
+ })
486
+ await new Promise((r) => setTimeout(r, 100))
463
487
 
464
- await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
465
- await new Promise(r => setTimeout(r, 100))
488
+ await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
489
+ await new Promise((r) => setTimeout(r, 100))
466
490
 
467
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
468
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.org'))
469
- expect(cdpPage).toBeDefined()
491
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
492
+ const cdpPage = browser
493
+ .contexts()[0]
494
+ .pages()
495
+ .find((p) => p.url().includes('example.org'))
496
+ expect(cdpPage).toBeDefined()
470
497
 
471
- const wsUrl = getCdpUrl({ port: TEST_PORT })
472
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
498
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
499
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
473
500
 
474
- const evalResult = await cdpSession.send('Runtime.evaluate', {
475
- expression: 'document.title',
476
- returnByValue: true,
477
- })
478
- expect(evalResult.result.value).toContain('Example Domain')
501
+ const evalResult = await cdpSession.send('Runtime.evaluate', {
502
+ expression: 'document.title',
503
+ returnByValue: true,
504
+ })
505
+ expect(evalResult.result.value).toContain('Example Domain')
479
506
 
480
- await cdpSession.detach()
481
- await browser.close()
482
- await page.close()
483
- }, 60000)
507
+ await cdpSession.detach()
508
+ await browser.close()
509
+ await page.close()
510
+ }, 60000)
484
511
 
485
- it('should maintain CDP session functionality after page URL change', async () => {
486
- const browserContext = getBrowserContext()
487
- const serviceWorker = await getExtensionServiceWorker(browserContext)
512
+ it('should maintain CDP session functionality after page URL change', async () => {
513
+ const browserContext = getBrowserContext()
514
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
488
515
 
489
- const page = await browserContext.newPage()
490
- const initialUrl = 'https://example.com/'
491
- await page.goto(initialUrl)
492
- await page.bringToFront()
516
+ const page = await browserContext.newPage()
517
+ const initialUrl = 'https://example.com/'
518
+ await page.goto(initialUrl)
519
+ await page.bringToFront()
493
520
 
494
- await serviceWorker.evaluate(async () => {
495
- await globalThis.toggleExtensionForActiveTab()
496
- })
497
- await new Promise(r => setTimeout(r, 100))
521
+ await serviceWorker.evaluate(async () => {
522
+ await globalThis.toggleExtensionForActiveTab()
523
+ })
524
+ await new Promise((r) => setTimeout(r, 100))
498
525
 
499
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
500
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
501
- expect(cdpPage).toBeDefined()
526
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
527
+ const cdpPage = browser
528
+ .contexts()[0]
529
+ .pages()
530
+ .find((p) => p.url().includes('example.com'))
531
+ expect(cdpPage).toBeDefined()
502
532
 
503
- const wsUrl = getCdpUrl({ port: TEST_PORT })
504
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
533
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
534
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
505
535
 
506
- const initialEvalResult = await cdpSession.send('Runtime.evaluate', {
507
- expression: 'document.title',
508
- returnByValue: true,
509
- })
510
- expect(initialEvalResult.result.value).toBe('Example Domain')
536
+ const initialEvalResult = await cdpSession.send('Runtime.evaluate', {
537
+ expression: 'document.title',
538
+ returnByValue: true,
539
+ })
540
+ expect(initialEvalResult.result.value).toBe('Example Domain')
511
541
 
512
- const newUrl = 'https://example.org/'
513
- await cdpPage!.goto(newUrl, { waitUntil: 'domcontentloaded' })
542
+ const newUrl = 'https://example.org/'
543
+ await cdpPage!.goto(newUrl, { waitUntil: 'domcontentloaded' })
514
544
 
515
- expect(cdpPage!.url()).toBe(newUrl)
545
+ expect(cdpPage!.url()).toBe(newUrl)
516
546
 
517
- const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
518
- expect(layoutMetrics.cssVisualViewport).toBeDefined()
519
- expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
547
+ const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
548
+ expect(layoutMetrics.cssVisualViewport).toBeDefined()
549
+ expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
520
550
 
521
- const afterNavEvalResult = await cdpSession.send('Runtime.evaluate', {
522
- expression: 'document.title',
523
- returnByValue: true,
524
- })
525
- expect(afterNavEvalResult.result.value).toContain('Example Domain')
551
+ const afterNavEvalResult = await cdpSession.send('Runtime.evaluate', {
552
+ expression: 'document.title',
553
+ returnByValue: true,
554
+ })
555
+ expect(afterNavEvalResult.result.value).toContain('Example Domain')
526
556
 
527
- const locationResult = await cdpSession.send('Runtime.evaluate', {
528
- expression: 'window.location.href',
529
- returnByValue: true,
530
- })
531
- expect(locationResult.result.value).toBe(newUrl)
557
+ const locationResult = await cdpSession.send('Runtime.evaluate', {
558
+ expression: 'window.location.href',
559
+ returnByValue: true,
560
+ })
561
+ expect(locationResult.result.value).toBe(newUrl)
532
562
 
533
- await cdpSession.detach()
534
- await browser.close()
535
- await page.close()
536
- }, 60000)
563
+ await cdpSession.detach()
564
+ await browser.close()
565
+ await page.close()
566
+ }, 60000)
537
567
 
538
- it('should pause on all exceptions with setPauseOnExceptions', async () => {
539
- const browserContext = getBrowserContext()
540
- const serviceWorker = await getExtensionServiceWorker(browserContext)
568
+ it('should pause on all exceptions with setPauseOnExceptions', async () => {
569
+ const browserContext = getBrowserContext()
570
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
541
571
 
542
- const page = await browserContext.newPage()
543
- await page.goto('https://example.com/')
544
- await page.bringToFront()
572
+ const page = await browserContext.newPage()
573
+ await page.goto('https://example.com/')
574
+ await page.bringToFront()
545
575
 
546
- await serviceWorker.evaluate(async () => {
547
- await globalThis.toggleExtensionForActiveTab()
548
- })
549
- await new Promise(r => setTimeout(r, 100))
576
+ await serviceWorker.evaluate(async () => {
577
+ await globalThis.toggleExtensionForActiveTab()
578
+ })
579
+ await new Promise((r) => setTimeout(r, 100))
550
580
 
551
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
552
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
553
- expect(cdpPage).toBeDefined()
581
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
582
+ const cdpPage = browser
583
+ .contexts()[0]
584
+ .pages()
585
+ .find((p) => p.url().includes('example.com'))
586
+ expect(cdpPage).toBeDefined()
554
587
 
555
- const wsUrl = getCdpUrl({ port: TEST_PORT })
556
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
557
- const dbg = new Debugger({ cdp: cdpSession })
588
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
589
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
590
+ const dbg = new Debugger({ cdp: cdpSession })
558
591
 
559
- await dbg.enable()
560
- await dbg.setPauseOnExceptions({ state: 'all' })
592
+ await dbg.enable()
593
+ await dbg.setPauseOnExceptions({ state: 'all' })
561
594
 
562
- const pausedPromise = new Promise<void>((resolve) => {
563
- cdpSession.on('Debugger.paused', () => resolve())
564
- })
595
+ const pausedPromise = new Promise<void>((resolve) => {
596
+ cdpSession.on('Debugger.paused', () => resolve())
597
+ })
565
598
 
566
- const evalPromise = cdpSession.send('Runtime.evaluate', {
567
- expression: `(function() {
599
+ const evalPromise = cdpSession.send('Runtime.evaluate', {
600
+ expression: `(function() {
568
601
  try {
569
602
  throw new Error('Caught test error');
570
603
  } catch (e) {
571
604
  // caught but should still pause with state 'all'
572
605
  }
573
- })()`
574
- })
606
+ })()`,
607
+ })
575
608
 
576
- await Promise.race([
577
- pausedPromise,
578
- new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
579
- ])
609
+ await Promise.race([
610
+ pausedPromise,
611
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
612
+ ])
580
613
 
581
- expect(dbg.isPaused()).toBe(true)
614
+ expect(dbg.isPaused()).toBe(true)
582
615
 
583
- const location = await dbg.getLocation()
584
- expect(location.sourceContext).toContain('throw')
616
+ const location = await dbg.getLocation()
617
+ expect(location.sourceContext).toContain('throw')
585
618
 
586
- await dbg.resume()
587
- await evalPromise
619
+ await dbg.resume()
620
+ await evalPromise
588
621
 
589
- await dbg.setPauseOnExceptions({ state: 'none' })
622
+ await dbg.setPauseOnExceptions({ state: 'none' })
590
623
 
591
- await cdpSession.detach()
592
- await browser.close()
593
- await page.close()
594
- }, 60000)
624
+ await cdpSession.detach()
625
+ await browser.close()
626
+ await page.close()
627
+ }, 60000)
595
628
 
596
- it('should inspect local and global variables with inline snapshots', async () => {
597
- const browserContext = getBrowserContext()
598
- const serviceWorker = await getExtensionServiceWorker(browserContext)
629
+ it('should inspect local and global variables with inline snapshots', async () => {
630
+ const browserContext = getBrowserContext()
631
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
599
632
 
600
- const page = await browserContext.newPage()
601
- await page.setContent(`
633
+ const page = await browserContext.newPage()
634
+ await page.setContent(`
602
635
  <html>
603
636
  <head>
604
637
  <script>
@@ -618,51 +651,51 @@ describe('CDP Session Tests', () => {
618
651
  </body>
619
652
  </html>
620
653
  `)
621
- await page.bringToFront()
654
+ await page.bringToFront()
622
655
 
623
- await serviceWorker.evaluate(async () => {
624
- await globalThis.toggleExtensionForActiveTab()
625
- })
626
- await new Promise(r => setTimeout(r, 100))
627
-
628
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
629
- let cdpPage
630
- for (const p of browser.contexts()[0].pages()) {
631
- const html = await p.content()
632
- if (html.includes('runTest')) {
633
- cdpPage = p
634
- break
635
- }
636
- }
637
- expect(cdpPage).toBeDefined()
656
+ await serviceWorker.evaluate(async () => {
657
+ await globalThis.toggleExtensionForActiveTab()
658
+ })
659
+ await new Promise((r) => setTimeout(r, 100))
660
+
661
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
662
+ let cdpPage
663
+ for (const p of browser.contexts()[0].pages()) {
664
+ const html = await p.content()
665
+ if (html.includes('runTest')) {
666
+ cdpPage = p
667
+ break
668
+ }
669
+ }
670
+ expect(cdpPage).toBeDefined()
638
671
 
639
- const wsUrl = getCdpUrl({ port: TEST_PORT })
640
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
641
- const dbg = new Debugger({ cdp: cdpSession })
672
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
673
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
674
+ const dbg = new Debugger({ cdp: cdpSession })
642
675
 
643
- await dbg.enable()
676
+ await dbg.enable()
644
677
 
645
- const globalVars = await dbg.inspectGlobalVariables()
646
- expect(globalVars).toMatchInlineSnapshot(`
678
+ const globalVars = await dbg.inspectGlobalVariables()
679
+ expect(globalVars).toMatchInlineSnapshot(`
647
680
  [
648
681
  "GLOBAL_CONFIG",
649
682
  ]
650
683
  `)
651
684
 
652
- const pausedPromise = new Promise<void>((resolve) => {
653
- cdpSession.on('Debugger.paused', () => resolve())
654
- })
685
+ const pausedPromise = new Promise<void>((resolve) => {
686
+ cdpSession.on('Debugger.paused', () => resolve())
687
+ })
655
688
 
656
- // Don't await - we want it to pause at breakpoint
657
- const evalPromise = cdpPage!.evaluate('runTest()').catch(() => {
658
- // Ignore errors from evaluate when browser closes
659
- })
689
+ // Don't await - we want it to pause at breakpoint
690
+ const evalPromise = cdpPage!.evaluate('runTest()').catch(() => {
691
+ // Ignore errors from evaluate when browser closes
692
+ })
660
693
 
661
- await pausedPromise
662
- expect(dbg.isPaused()).toBe(true)
694
+ await pausedPromise
695
+ expect(dbg.isPaused()).toBe(true)
663
696
 
664
- const localVars = await dbg.inspectLocalVariables()
665
- expect(localVars).toMatchInlineSnapshot(`
697
+ const localVars = await dbg.inspectLocalVariables()
698
+ expect(localVars).toMatchInlineSnapshot(`
666
699
  {
667
700
  "GLOBAL_CONFIG": "production",
668
701
  "scores": "[array]",
@@ -672,204 +705,213 @@ describe('CDP Session Tests', () => {
672
705
  }
673
706
  `)
674
707
 
675
- await dbg.resume()
676
- // Wait for evaluate to complete after resume
677
- await evalPromise
678
-
679
- await cdpSession.detach()
680
- await browser.close()
681
- await page.close()
682
- }, 60000)
683
-
684
- it('should click at correct coordinates on high-DPI simulation', async () => {
685
- const browserContext = getBrowserContext()
686
- const serviceWorker = await getExtensionServiceWorker(browserContext)
708
+ await dbg.resume()
709
+ // Wait for evaluate to complete after resume
710
+ await evalPromise
687
711
 
688
- const page = await browserContext.newPage()
689
- await page.goto('https://example.com/')
690
- await page.bringToFront()
712
+ await cdpSession.detach()
713
+ await browser.close()
714
+ await page.close()
715
+ }, 60000)
691
716
 
692
- await serviceWorker.evaluate(async () => {
693
- await globalThis.toggleExtensionForActiveTab()
694
- })
695
- await new Promise(r => setTimeout(r, 100))
696
-
697
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
698
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
699
- expect(cdpPage).toBeDefined()
717
+ it('should click at correct coordinates on high-DPI simulation', async () => {
718
+ const browserContext = getBrowserContext()
719
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
700
720
 
701
- const h1Bounds = await cdpPage!.locator('h1').boundingBox()
702
- expect(h1Bounds).toBeDefined()
703
- console.log('H1 bounding box:', h1Bounds)
721
+ const page = await browserContext.newPage()
722
+ await page.goto('https://example.com/')
723
+ await page.bringToFront()
704
724
 
705
- await cdpPage!.evaluate(() => {
706
- (window as any).clickedAt = null;
707
- document.addEventListener('click', (e) => {
708
- (window as any).clickedAt = { x: e.clientX, y: e.clientY };
709
- });
710
- })
725
+ await serviceWorker.evaluate(async () => {
726
+ await globalThis.toggleExtensionForActiveTab()
727
+ })
728
+ await new Promise((r) => setTimeout(r, 100))
729
+
730
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
731
+ const cdpPage = browser
732
+ .contexts()[0]
733
+ .pages()
734
+ .find((p) => p.url().includes('example.com'))
735
+ expect(cdpPage).toBeDefined()
736
+
737
+ const h1Bounds = await cdpPage!.locator('h1').boundingBox()
738
+ expect(h1Bounds).toBeDefined()
739
+ console.log('H1 bounding box:', h1Bounds)
740
+
741
+ await cdpPage!.evaluate(() => {
742
+ ;(window as any).clickedAt = null
743
+ document.addEventListener('click', (e) => {
744
+ ;(window as any).clickedAt = { x: e.clientX, y: e.clientY }
745
+ })
746
+ })
711
747
 
712
- await cdpPage!.locator('h1').click()
748
+ await cdpPage!.locator('h1').click()
713
749
 
714
- const clickedAt = await cdpPage!.evaluate(() => (window as any).clickedAt)
715
- console.log('Clicked at:', clickedAt)
750
+ const clickedAt = await cdpPage!.evaluate(() => (window as any).clickedAt)
751
+ console.log('Clicked at:', clickedAt)
716
752
 
717
- expect(clickedAt).toBeDefined()
718
- expect(clickedAt.x).toBeGreaterThan(0)
719
- expect(clickedAt.y).toBeGreaterThan(0)
753
+ expect(clickedAt).toBeDefined()
754
+ expect(clickedAt.x).toBeGreaterThan(0)
755
+ expect(clickedAt.y).toBeGreaterThan(0)
720
756
 
721
- await browser.close()
722
- await page.close()
723
- }, 60000)
757
+ await browser.close()
758
+ await page.close()
759
+ }, 60000)
724
760
 
725
- it('should use Editor class to list, read, and edit scripts', async () => {
726
- const browserContext = getBrowserContext()
727
- const serviceWorker = await getExtensionServiceWorker(browserContext)
761
+ it('should use Editor class to list, read, and edit scripts', async () => {
762
+ const browserContext = getBrowserContext()
763
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
728
764
 
729
- const page = await browserContext.newPage()
730
- await page.goto('https://example.com/')
731
- await page.bringToFront()
765
+ const page = await browserContext.newPage()
766
+ await page.goto('https://example.com/')
767
+ await page.bringToFront()
732
768
 
733
- await serviceWorker.evaluate(async () => {
734
- await globalThis.toggleExtensionForActiveTab()
735
- })
736
- await new Promise(r => setTimeout(r, 100))
769
+ await serviceWorker.evaluate(async () => {
770
+ await globalThis.toggleExtensionForActiveTab()
771
+ })
772
+ await new Promise((r) => setTimeout(r, 100))
737
773
 
738
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
739
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
740
- expect(cdpPage).toBeDefined()
774
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
775
+ const cdpPage = browser
776
+ .contexts()[0]
777
+ .pages()
778
+ .find((p) => p.url().includes('example.com'))
779
+ expect(cdpPage).toBeDefined()
741
780
 
742
- const wsUrl = getCdpUrl({ port: TEST_PORT })
743
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
744
- const editor = new Editor({ cdp: cdpSession })
781
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
782
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
783
+ const editor = new Editor({ cdp: cdpSession })
745
784
 
746
- await editor.enable()
785
+ await editor.enable()
747
786
 
748
- await cdpPage!.addScriptTag({
749
- content: `
787
+ await cdpPage!.addScriptTag({
788
+ content: `
750
789
  function greetUser(name) {
751
790
  console.log('Hello, ' + name);
752
791
  return 'Hello, ' + name;
753
792
  }
754
793
  `,
755
- })
756
- await new Promise(r => setTimeout(r, 100))
757
- const scripts = await editor.list()
758
- expect(scripts.length).toBeGreaterThan(0)
759
-
760
- const matches = await editor.grep({ regex: /greetUser/ })
761
- expect(matches.length).toBeGreaterThan(0)
762
-
763
- const match = matches[0]
764
- const { content, totalLines } = await editor.read({ url: match.url })
765
- expect(content).toContain('greetUser')
766
- expect(totalLines).toBeGreaterThan(0)
767
-
768
- await editor.edit({
769
- url: match.url,
770
- oldString: "console.log('Hello, ' + name);",
771
- newString: "console.log('Hello, ' + name); console.log('EDITOR_TEST_MARKER');",
772
- })
794
+ })
795
+ await new Promise((r) => setTimeout(r, 100))
796
+ const scripts = await editor.list()
797
+ expect(scripts.length).toBeGreaterThan(0)
798
+
799
+ const matches = await editor.grep({ regex: /greetUser/ })
800
+ expect(matches.length).toBeGreaterThan(0)
801
+
802
+ const match = matches[0]
803
+ const { content, totalLines } = await editor.read({ url: match.url })
804
+ expect(content).toContain('greetUser')
805
+ expect(totalLines).toBeGreaterThan(0)
806
+
807
+ await editor.edit({
808
+ url: match.url,
809
+ oldString: "console.log('Hello, ' + name);",
810
+ newString: "console.log('Hello, ' + name); console.log('EDITOR_TEST_MARKER');",
811
+ })
773
812
 
774
- const consoleLogs: string[] = []
775
- cdpPage!.on('console', msg => {
776
- consoleLogs.push(msg.text())
777
- })
813
+ const consoleLogs: string[] = []
814
+ cdpPage!.on('console', (msg) => {
815
+ consoleLogs.push(msg.text())
816
+ })
778
817
 
779
- await cdpPage!.evaluate(() => {
780
- (window as any).greetUser('World')
781
- })
782
- await new Promise(r => setTimeout(r, 100))
818
+ await cdpPage!.evaluate(() => {
819
+ ;(window as any).greetUser('World')
820
+ })
821
+ await new Promise((r) => setTimeout(r, 100))
783
822
 
784
- expect(consoleLogs).toContain('Hello, World')
785
- expect(consoleLogs).toContain('EDITOR_TEST_MARKER')
823
+ expect(consoleLogs).toContain('Hello, World')
824
+ expect(consoleLogs).toContain('EDITOR_TEST_MARKER')
786
825
 
787
- await cdpSession.detach()
788
- await browser.close()
789
- await page.close()
790
- }, 60000)
826
+ await cdpSession.detach()
827
+ await browser.close()
828
+ await page.close()
829
+ }, 60000)
791
830
 
792
- it('editor can list, read, and edit CSS stylesheets', async () => {
793
- const browserContext = getBrowserContext()
794
- const serviceWorker = await getExtensionServiceWorker(browserContext)
831
+ it('editor can list, read, and edit CSS stylesheets', async () => {
832
+ const browserContext = getBrowserContext()
833
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
795
834
 
796
- const page = await browserContext.newPage()
797
- await page.goto('https://example.com/')
798
- await page.bringToFront()
835
+ const page = await browserContext.newPage()
836
+ await page.goto('https://example.com/')
837
+ await page.bringToFront()
799
838
 
800
- await serviceWorker.evaluate(async () => {
801
- await globalThis.toggleExtensionForActiveTab()
802
- })
803
- await new Promise(r => setTimeout(r, 100))
839
+ await serviceWorker.evaluate(async () => {
840
+ await globalThis.toggleExtensionForActiveTab()
841
+ })
842
+ await new Promise((r) => setTimeout(r, 100))
804
843
 
805
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
806
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
807
- expect(cdpPage).toBeDefined()
844
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
845
+ const cdpPage = browser
846
+ .contexts()[0]
847
+ .pages()
848
+ .find((p) => p.url().includes('example.com'))
849
+ expect(cdpPage).toBeDefined()
808
850
 
809
- const wsUrl = getCdpUrl({ port: TEST_PORT })
810
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
811
- const editor = new Editor({ cdp: cdpSession })
851
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
852
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
853
+ const editor = new Editor({ cdp: cdpSession })
812
854
 
813
- await editor.enable()
855
+ await editor.enable()
814
856
 
815
- await cdpPage!.addStyleTag({
816
- content: `
857
+ await cdpPage!.addStyleTag({
858
+ content: `
817
859
  .editor-test-element {
818
860
  color: rgb(255, 0, 0);
819
861
  background-color: rgb(0, 0, 255);
820
862
  }
821
863
  `,
822
- })
823
- await new Promise(r => setTimeout(r, 100))
824
- const stylesheets = await editor.list({ pattern: /inline-css:/ })
825
- expect(stylesheets.length).toBeGreaterThan(0)
826
-
827
- const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ })
828
- expect(cssMatches.length).toBeGreaterThan(0)
829
-
830
- const cssMatch = cssMatches[0]
831
- const { content, totalLines } = await editor.read({ url: cssMatch.url })
832
- expect(content).toContain('editor-test-element')
833
- expect(content).toContain('rgb(255, 0, 0)')
834
- expect(totalLines).toBeGreaterThan(0)
835
-
836
- await cdpPage!.evaluate(() => {
837
- const el = document.createElement('div')
838
- el.className = 'editor-test-element'
839
- el.id = 'test-div'
840
- el.textContent = 'Test'
841
- document.body.appendChild(el)
842
- })
864
+ })
865
+ await new Promise((r) => setTimeout(r, 100))
866
+ const stylesheets = await editor.list({ pattern: /inline-css:/ })
867
+ expect(stylesheets.length).toBeGreaterThan(0)
868
+
869
+ const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ })
870
+ expect(cssMatches.length).toBeGreaterThan(0)
871
+
872
+ const cssMatch = cssMatches[0]
873
+ const { content, totalLines } = await editor.read({ url: cssMatch.url })
874
+ expect(content).toContain('editor-test-element')
875
+ expect(content).toContain('rgb(255, 0, 0)')
876
+ expect(totalLines).toBeGreaterThan(0)
877
+
878
+ await cdpPage!.evaluate(() => {
879
+ const el = document.createElement('div')
880
+ el.className = 'editor-test-element'
881
+ el.id = 'test-div'
882
+ el.textContent = 'Test'
883
+ document.body.appendChild(el)
884
+ })
843
885
 
844
- const colorBefore = await cdpPage!.evaluate(() => {
845
- const el = document.getElementById('test-div')!
846
- return window.getComputedStyle(el).color
847
- })
848
- expect(colorBefore).toBe('rgb(255, 0, 0)')
886
+ const colorBefore = await cdpPage!.evaluate(() => {
887
+ const el = document.getElementById('test-div')!
888
+ return window.getComputedStyle(el).color
889
+ })
890
+ expect(colorBefore).toBe('rgb(255, 0, 0)')
849
891
 
850
- await editor.edit({
851
- url: cssMatch.url,
852
- oldString: 'color: rgb(255, 0, 0);',
853
- newString: 'color: rgb(0, 255, 0);',
854
- })
892
+ await editor.edit({
893
+ url: cssMatch.url,
894
+ oldString: 'color: rgb(255, 0, 0);',
895
+ newString: 'color: rgb(0, 255, 0);',
896
+ })
855
897
 
856
- const colorAfter = await cdpPage!.evaluate(() => {
857
- const el = document.getElementById('test-div')!
858
- return window.getComputedStyle(el).color
859
- })
860
- expect(colorAfter).toBe('rgb(0, 255, 0)')
898
+ const colorAfter = await cdpPage!.evaluate(() => {
899
+ const el = document.getElementById('test-div')!
900
+ return window.getComputedStyle(el).color
901
+ })
902
+ expect(colorAfter).toBe('rgb(0, 255, 0)')
861
903
 
862
- await cdpSession.detach()
863
- await browser.close()
864
- await page.close()
865
- }, 60000)
904
+ await cdpSession.detach()
905
+ await browser.close()
906
+ await page.close()
907
+ }, 60000)
866
908
 
867
- it('should inject bippy and find React fiber with getReactSource', async () => {
868
- const browserContext = getBrowserContext()
869
- const serviceWorker = await getExtensionServiceWorker(browserContext)
909
+ it('should inject bippy and find React fiber with getReactSource', async () => {
910
+ const browserContext = getBrowserContext()
911
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
870
912
 
871
- const page = await browserContext.newPage()
872
- await page.setContent(`
913
+ const page = await browserContext.newPage()
914
+ await page.setContent(`
873
915
  <!DOCTYPE html>
874
916
  <html>
875
917
  <head>
@@ -888,399 +930,412 @@ describe('CDP Session Tests', () => {
888
930
  </body>
889
931
  </html>
890
932
  `)
891
- await page.bringToFront()
933
+ await page.bringToFront()
892
934
 
893
- await serviceWorker.evaluate(async () => {
894
- await globalThis.toggleExtensionForActiveTab()
895
- })
896
- await new Promise(r => setTimeout(r, 500))
935
+ await serviceWorker.evaluate(async () => {
936
+ await globalThis.toggleExtensionForActiveTab()
937
+ })
938
+ await new Promise((r) => setTimeout(r, 500))
897
939
 
898
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
899
- const pages = browser.contexts()[0].pages()
900
- const cdpPage = pages.find(p => p.url().startsWith('about:'))
901
- expect(cdpPage).toBeDefined()
940
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
941
+ const pages = browser.contexts()[0].pages()
942
+ const cdpPage = pages.find((p) => p.url().startsWith('about:'))
943
+ expect(cdpPage).toBeDefined()
902
944
 
903
- const btn = cdpPage!.locator('#react-btn')
904
- const btnCount = await btn.count()
905
- expect(btnCount).toBe(1)
945
+ const btn = cdpPage!.locator('#react-btn')
946
+ const btnCount = await btn.count()
947
+ expect(btnCount).toBe(1)
906
948
 
907
- const hasBippyBefore = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
908
- expect(hasBippyBefore).toBe(false)
949
+ const hasBippyBefore = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
950
+ expect(hasBippyBefore).toBe(false)
909
951
 
910
- const wsUrl = getCdpUrl({ port: TEST_PORT })
911
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
952
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
953
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
912
954
 
913
- const { getReactSource } = await import('./react-source.js')
914
- const source = await getReactSource({ locator: btn, cdp: cdpSession })
955
+ const { getReactSource } = await import('./react-source.js')
956
+ const source = await getReactSource({ locator: btn, cdp: cdpSession })
915
957
 
916
- const hasBippyAfter = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
917
- expect(hasBippyAfter).toBe(true)
958
+ const hasBippyAfter = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
959
+ expect(hasBippyAfter).toBe(true)
918
960
 
919
- const hasFiber = await btn.evaluate((el) => {
920
- const bippy = (globalThis as any).__bippy
921
- const fiber = bippy.getFiberFromHostInstance(el)
922
- return !!fiber
923
- })
924
- expect(hasFiber).toBe(true)
925
-
926
- const componentName = await btn.evaluate((el) => {
927
- const bippy = (globalThis as any).__bippy
928
- const fiber = bippy.getFiberFromHostInstance(el)
929
- let current = fiber
930
- while (current) {
931
- if (bippy.isCompositeFiber(current)) {
932
- return bippy.getDisplayName(current.type)
933
- }
934
- current = current.return
935
- }
936
- return null
937
- })
938
- expect(componentName).toBe('MyComponent')
961
+ const hasFiber = await btn.evaluate((el) => {
962
+ const bippy = (globalThis as any).__bippy
963
+ const fiber = bippy.getFiberFromHostInstance(el)
964
+ return !!fiber
965
+ })
966
+ expect(hasFiber).toBe(true)
967
+
968
+ const componentName = await btn.evaluate((el) => {
969
+ const bippy = (globalThis as any).__bippy
970
+ const fiber = bippy.getFiberFromHostInstance(el)
971
+ let current = fiber
972
+ while (current) {
973
+ if (bippy.isCompositeFiber(current)) {
974
+ return bippy.getDisplayName(current.type)
975
+ }
976
+ current = current.return
977
+ }
978
+ return null
979
+ })
980
+ expect(componentName).toBe('MyComponent')
939
981
 
940
- console.log('Component name from fiber:', componentName)
941
- console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source)
982
+ console.log('Component name from fiber:', componentName)
983
+ console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source)
942
984
 
943
- await browser.close()
944
- await page.close()
945
- }, 60000)
985
+ await browser.close()
986
+ await page.close()
987
+ }, 60000)
946
988
  })
947
989
 
948
990
  // --- Service Worker Target Tests ---
949
991
 
950
992
  describe('Service Worker Target Tests', () => {
951
- let testCtx: TestContext | null = null
952
-
953
- beforeAll(async () => {
954
- testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-sw-test-', toggleExtension: true })
955
- }, 600000)
956
-
957
- afterAll(async () => {
958
- await cleanupTestContext(testCtx)
959
- testCtx = null
960
- })
993
+ let testCtx: TestContext | null = null
961
994
 
962
- const getBrowserContext = () => {
963
- if (!testCtx?.browserContext) throw new Error('Browser not initialized')
964
- return testCtx.browserContext
965
- }
995
+ beforeAll(async () => {
996
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-sw-test-', toggleExtension: true })
997
+ }, 600000)
966
998
 
967
- it('should not expose service worker targets to Playwright (issue #14)', async () => {
968
- const browserContext = getBrowserContext()
969
- const serviceWorker = await getExtensionServiceWorker(browserContext)
999
+ afterAll(async () => {
1000
+ await cleanupTestContext(testCtx)
1001
+ testCtx = null
1002
+ })
970
1003
 
971
- const page = await browserContext.newPage()
972
- await page.goto('https://web.dev/', { waitUntil: 'load' })
973
- await page.bringToFront()
1004
+ const getBrowserContext = () => {
1005
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
1006
+ return testCtx.browserContext
1007
+ }
974
1008
 
975
- await serviceWorker.evaluate(async () => {
976
- await globalThis.toggleExtensionForActiveTab()
977
- })
978
- await new Promise(r => setTimeout(r, 500))
1009
+ it('should not expose service worker targets to Playwright (issue #14)', async () => {
1010
+ const browserContext = getBrowserContext()
1011
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
979
1012
 
980
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
981
- const context = browser.contexts()[0]
1013
+ const page = await browserContext.newPage()
1014
+ await page.goto('https://web.dev/', { waitUntil: 'load' })
1015
+ await page.bringToFront()
982
1016
 
983
- const pages = context.pages()
984
-
985
- for (const p of pages) {
986
- const url = p.url()
987
- console.log('Page URL:', url)
988
- expect(url).not.toMatch(/sw\.js$/i)
989
- expect(url).not.toMatch(/service.?worker/i)
990
- }
1017
+ await serviceWorker.evaluate(async () => {
1018
+ await globalThis.toggleExtensionForActiveTab()
1019
+ })
1020
+ await new Promise((r) => setTimeout(r, 500))
991
1021
 
992
- const targetPage = pages.find(p => p.url().includes('web.dev'))
993
- expect(targetPage).toBeDefined()
1022
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1023
+ const context = browser.contexts()[0]
994
1024
 
995
- const title = await targetPage!.title()
996
- expect(title).toBeTruthy()
1025
+ const pages = context.pages()
997
1026
 
998
- await safeCloseCDPBrowser(browser)
999
- await page.close()
1000
- }, 60000)
1027
+ for (const p of pages) {
1028
+ const url = p.url()
1029
+ console.log('Page URL:', url)
1030
+ expect(url).not.toMatch(/sw\.js$/i)
1031
+ expect(url).not.toMatch(/service.?worker/i)
1032
+ }
1001
1033
 
1002
- it('should allow reading response bodies after re-enabling Network buffering', async () => {
1003
- const browserContext = getBrowserContext()
1004
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1034
+ const targetPage = pages.find((p) => p.url().includes('web.dev'))
1035
+ expect(targetPage).toBeDefined()
1005
1036
 
1006
- const page = await browserContext.newPage()
1007
- await page.goto('https://example.com/')
1008
- await page.bringToFront()
1037
+ const title = await targetPage!.title()
1038
+ expect(title).toBeTruthy()
1009
1039
 
1010
- await serviceWorker.evaluate(async () => {
1011
- await globalThis.toggleExtensionForActiveTab()
1012
- })
1013
- await new Promise(r => setTimeout(r, 100))
1040
+ await safeCloseCDPBrowser(browser)
1041
+ await page.close()
1042
+ }, 60000)
1014
1043
 
1015
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1016
- const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
1017
- expect(cdpPage).toBeDefined()
1044
+ it('should allow reading response bodies after re-enabling Network buffering', async () => {
1045
+ const browserContext = getBrowserContext()
1046
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1018
1047
 
1019
- const wsUrl = getCdpUrl({ port: TEST_PORT })
1020
- const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
1048
+ const page = await browserContext.newPage()
1049
+ await page.goto('https://example.com/')
1050
+ await page.bringToFront()
1021
1051
 
1022
- await cdpSession.send('Network.disable')
1023
- await cdpSession.send('Network.enable', {
1024
- maxTotalBufferSize: 10000000,
1025
- maxResourceBufferSize: 5000000
1026
- })
1052
+ await serviceWorker.evaluate(async () => {
1053
+ await globalThis.toggleExtensionForActiveTab()
1054
+ })
1055
+ await new Promise((r) => setTimeout(r, 100))
1056
+
1057
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1058
+ const cdpPage = browser
1059
+ .contexts()[0]
1060
+ .pages()
1061
+ .find((p) => p.url().includes('example.com'))
1062
+ expect(cdpPage).toBeDefined()
1063
+
1064
+ const wsUrl = getCdpUrl({ port: TEST_PORT })
1065
+ const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
1066
+
1067
+ await cdpSession.send('Network.disable')
1068
+ await cdpSession.send('Network.enable', {
1069
+ maxTotalBufferSize: 10000000,
1070
+ maxResourceBufferSize: 5000000,
1071
+ })
1027
1072
 
1028
- const [response] = await Promise.all([
1029
- cdpPage!.waitForResponse(resp => resp.url() === 'https://example.com/'),
1030
- cdpPage!.goto('https://example.com/')
1031
- ])
1073
+ const [response] = await Promise.all([
1074
+ cdpPage!.waitForResponse((resp) => resp.url() === 'https://example.com/'),
1075
+ cdpPage!.goto('https://example.com/'),
1076
+ ])
1032
1077
 
1033
- const body = await response.text()
1078
+ const body = await response.text()
1034
1079
 
1035
- expect(body).toBeDefined()
1036
- expect(body).toContain('Example Domain')
1037
- expect(body).toContain('</html>')
1080
+ expect(body).toBeDefined()
1081
+ expect(body).toContain('Example Domain')
1082
+ expect(body).toContain('</html>')
1038
1083
 
1039
- await cdpSession.detach()
1040
- await browser.close()
1041
- await page.close()
1042
- }, 60000)
1084
+ await cdpSession.detach()
1085
+ await browser.close()
1086
+ await page.close()
1087
+ }, 60000)
1043
1088
 
1044
- it('should stream SSE without waiting for response end', async () => {
1045
- const browserContext = getBrowserContext()
1046
- const serviceWorker = await withTimeout({
1047
- promise: getExtensionServiceWorker(browserContext),
1048
- timeoutMs: 5000,
1049
- errorMessage: 'getExtensionServiceWorker timed out',
1050
- })
1051
- const sseServer = await withTimeout({
1052
- promise: createSseServer(),
1053
- timeoutMs: 5000,
1054
- errorMessage: 'createSseServer timed out',
1055
- })
1056
- let page: Awaited<ReturnType<typeof browserContext.newPage>> | null = null
1057
- let browser: Awaited<ReturnType<typeof chromium.connectOverCDP>> | null = null
1058
-
1059
- try {
1060
- page = await withTimeout({
1061
- promise: browserContext.newPage(),
1062
- timeoutMs: 5000,
1063
- errorMessage: 'newPage timed out',
1064
- })
1065
- await withTimeout({
1066
- promise: page.goto(`${sseServer.baseUrl}/`),
1067
- timeoutMs: 5000,
1068
- errorMessage: 'page.goto timed out',
1069
- })
1070
- await page.bringToFront()
1071
-
1072
- await withTimeout({
1073
- promise: serviceWorker.evaluate(async () => {
1074
- await globalThis.toggleExtensionForActiveTab()
1075
- }),
1076
- timeoutMs: 5000,
1077
- errorMessage: 'toggleExtensionForActiveTab timed out',
1078
- })
1079
- await new Promise((resolve) => {
1080
- setTimeout(resolve, 100)
1081
- })
1082
-
1083
- browser = await withTimeout({
1084
- promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
1085
- timeoutMs: 5000,
1086
- errorMessage: 'connectOverCDP timed out',
1087
- })
1088
- const cdpPage = browser.contexts()[0].pages().find((p) => {
1089
- return p.url().startsWith(sseServer.baseUrl)
1090
- })
1091
- expect(cdpPage).toBeDefined()
1092
-
1093
- await cdpPage!.evaluate(() => {
1094
- return window.startSse()
1095
- })
1096
- await withTimeout({
1097
- promise: cdpPage!.waitForFunction(() => {
1098
- return window.__sseMessages.length > 0
1099
- }, { timeout: 5000 }),
1100
- timeoutMs: 7000,
1101
- errorMessage: 'SSE message not received in time',
1102
- })
1103
-
1104
- const firstMessage = await cdpPage!.evaluate(() => {
1105
- return window.__sseMessages[0]
1106
- })
1107
- expect(firstMessage).toBe('hello')
1108
-
1109
- const sseState = sseServer.getState()
1110
- expect(sseState.connected).toBe(true)
1111
- expect(sseState.finished).toBe(false)
1112
- expect(sseState.closed).toBe(false)
1113
- expect(sseState.writeCount).toBeGreaterThan(0)
1114
-
1115
- const readyState = await cdpPage!.evaluate(() => {
1116
- if (!window.__sseSource) {
1117
- return -1
1118
- }
1119
- return window.__sseSource.readyState
1120
- })
1121
- expect(readyState).toBe(1)
1122
-
1123
- await cdpPage!.evaluate(() => {
1124
- window.stopSse()
1125
- })
1126
- await new Promise((resolve) => {
1127
- setTimeout(resolve, 100)
1128
- })
1129
- } finally {
1130
- if (browser) {
1131
- await withTimeout({
1132
- promise: browser.close(),
1133
- timeoutMs: 5000,
1134
- errorMessage: 'browser.close timed out',
1135
- })
1136
- }
1137
- if (page) {
1138
- await withTimeout({
1139
- promise: page.close(),
1140
- timeoutMs: 5000,
1141
- errorMessage: 'page.close timed out',
1142
- })
1143
- }
1144
- await withTimeout({
1145
- promise: sseServer.close(),
1146
- timeoutMs: 5000,
1147
- errorMessage: 'sseServer.close timed out',
1148
- })
1089
+ it('should stream SSE without waiting for response end', async () => {
1090
+ const browserContext = getBrowserContext()
1091
+ const serviceWorker = await withTimeout({
1092
+ promise: getExtensionServiceWorker(browserContext),
1093
+ timeoutMs: 5000,
1094
+ errorMessage: 'getExtensionServiceWorker timed out',
1095
+ })
1096
+ const sseServer = await withTimeout({
1097
+ promise: createSseServer(),
1098
+ timeoutMs: 5000,
1099
+ errorMessage: 'createSseServer timed out',
1100
+ })
1101
+ let page: Awaited<ReturnType<typeof browserContext.newPage>> | null = null
1102
+ let browser: Awaited<ReturnType<typeof chromium.connectOverCDP>> | null = null
1103
+
1104
+ try {
1105
+ page = await withTimeout({
1106
+ promise: browserContext.newPage(),
1107
+ timeoutMs: 5000,
1108
+ errorMessage: 'newPage timed out',
1109
+ })
1110
+ await withTimeout({
1111
+ promise: page.goto(`${sseServer.baseUrl}/`),
1112
+ timeoutMs: 5000,
1113
+ errorMessage: 'page.goto timed out',
1114
+ })
1115
+ await page.bringToFront()
1116
+
1117
+ await withTimeout({
1118
+ promise: serviceWorker.evaluate(async () => {
1119
+ await globalThis.toggleExtensionForActiveTab()
1120
+ }),
1121
+ timeoutMs: 5000,
1122
+ errorMessage: 'toggleExtensionForActiveTab timed out',
1123
+ })
1124
+ await new Promise((resolve) => {
1125
+ setTimeout(resolve, 100)
1126
+ })
1127
+
1128
+ browser = await withTimeout({
1129
+ promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
1130
+ timeoutMs: 5000,
1131
+ errorMessage: 'connectOverCDP timed out',
1132
+ })
1133
+ const cdpPage = browser
1134
+ .contexts()[0]
1135
+ .pages()
1136
+ .find((p) => {
1137
+ return p.url().startsWith(sseServer.baseUrl)
1138
+ })
1139
+ expect(cdpPage).toBeDefined()
1140
+
1141
+ await cdpPage!.evaluate(() => {
1142
+ return window.startSse()
1143
+ })
1144
+ await withTimeout({
1145
+ promise: cdpPage!.waitForFunction(
1146
+ () => {
1147
+ return window.__sseMessages.length > 0
1148
+ },
1149
+ { timeout: 5000 },
1150
+ ),
1151
+ timeoutMs: 7000,
1152
+ errorMessage: 'SSE message not received in time',
1153
+ })
1154
+
1155
+ const firstMessage = await cdpPage!.evaluate(() => {
1156
+ return window.__sseMessages[0]
1157
+ })
1158
+ expect(firstMessage).toBe('hello')
1159
+
1160
+ const sseState = sseServer.getState()
1161
+ expect(sseState.connected).toBe(true)
1162
+ expect(sseState.finished).toBe(false)
1163
+ expect(sseState.closed).toBe(false)
1164
+ expect(sseState.writeCount).toBeGreaterThan(0)
1165
+
1166
+ const readyState = await cdpPage!.evaluate(() => {
1167
+ if (!window.__sseSource) {
1168
+ return -1
1149
1169
  }
1150
- }, 60000)
1170
+ return window.__sseSource.readyState
1171
+ })
1172
+ expect(readyState).toBe(1)
1173
+
1174
+ await cdpPage!.evaluate(() => {
1175
+ window.stopSse()
1176
+ })
1177
+ await new Promise((resolve) => {
1178
+ setTimeout(resolve, 100)
1179
+ })
1180
+ } finally {
1181
+ if (browser) {
1182
+ await withTimeout({
1183
+ promise: browser.close(),
1184
+ timeoutMs: 5000,
1185
+ errorMessage: 'browser.close timed out',
1186
+ })
1187
+ }
1188
+ if (page) {
1189
+ await withTimeout({
1190
+ promise: page.close(),
1191
+ timeoutMs: 5000,
1192
+ errorMessage: 'page.close timed out',
1193
+ })
1194
+ }
1195
+ await withTimeout({
1196
+ promise: sseServer.close(),
1197
+ timeoutMs: 5000,
1198
+ errorMessage: 'sseServer.close timed out',
1199
+ })
1200
+ }
1201
+ }, 60000)
1151
1202
  })
1152
1203
 
1153
1204
  // --- Auto-enable Tests ---
1154
1205
 
1155
1206
  describe('Auto-enable Tests', () => {
1156
- let testCtx: TestContext | null = null
1157
- let client: Awaited<ReturnType<typeof createMCPClient>>['client']
1158
- let cleanup: (() => Promise<void>) | null = null
1159
-
1160
- beforeAll(async () => {
1161
- process.env.PLAYWRITER_AUTO_ENABLE = '1'
1162
- testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-auto-test-' })
1163
-
1164
- const result = await createMCPClient({ port: TEST_PORT })
1165
- client = result.client
1166
- cleanup = result.cleanup
1167
-
1168
- // Disconnect all tabs to start with a clean state
1169
- const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
1170
- await serviceWorker.evaluate(async () => {
1171
- await globalThis.disconnectEverything()
1172
- })
1173
- await new Promise(r => setTimeout(r, 100))
1174
- }, 600000)
1175
-
1176
- afterAll(async () => {
1177
- delete process.env.PLAYWRITER_AUTO_ENABLE
1178
- await cleanupTestContext(testCtx, cleanup)
1179
- cleanup = null
1180
- testCtx = null
1207
+ let testCtx: TestContext | null = null
1208
+ let client: Awaited<ReturnType<typeof createMCPClient>>['client']
1209
+ let cleanup: (() => Promise<void>) | null = null
1210
+
1211
+ beforeAll(async () => {
1212
+ process.env.PLAYWRITER_AUTO_ENABLE = '1'
1213
+ testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-auto-test-' })
1214
+
1215
+ const result = await createMCPClient({ port: TEST_PORT })
1216
+ client = result.client
1217
+ cleanup = result.cleanup
1218
+
1219
+ // Disconnect all tabs to start with a clean state
1220
+ const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
1221
+ await serviceWorker.evaluate(async () => {
1222
+ await globalThis.disconnectEverything()
1181
1223
  })
1224
+ await new Promise((r) => setTimeout(r, 100))
1225
+ }, 600000)
1226
+
1227
+ afterAll(async () => {
1228
+ delete process.env.PLAYWRITER_AUTO_ENABLE
1229
+ await cleanupTestContext(testCtx, cleanup)
1230
+ cleanup = null
1231
+ testCtx = null
1232
+ })
1233
+
1234
+ const getBrowserContext = () => {
1235
+ if (!testCtx?.browserContext) throw new Error('Browser not initialized')
1236
+ return testCtx.browserContext
1237
+ }
1238
+
1239
+ it('should auto-create a tab when Playwright connects and no tabs exist', async () => {
1240
+ const browserContext = getBrowserContext()
1241
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1242
+
1243
+ await serviceWorker.evaluate(async () => {
1244
+ await globalThis.disconnectEverything()
1245
+ })
1246
+ await new Promise((r) => setTimeout(r, 100))
1182
1247
 
1183
- const getBrowserContext = () => {
1184
- if (!testCtx?.browserContext) throw new Error('Browser not initialized')
1185
- return testCtx.browserContext
1186
- }
1187
-
1188
- it('should auto-create a tab when Playwright connects and no tabs exist', async () => {
1189
- const browserContext = getBrowserContext()
1190
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1191
-
1192
- await serviceWorker.evaluate(async () => {
1193
- await globalThis.disconnectEverything()
1194
- })
1195
- await new Promise(r => setTimeout(r, 100))
1196
-
1197
- const tabCountBefore = await serviceWorker.evaluate(() => {
1198
- const state = globalThis.getExtensionState()
1199
- return state.tabs.size
1200
- })
1201
- expect(tabCountBefore).toBe(0)
1248
+ const tabCountBefore = await serviceWorker.evaluate(() => {
1249
+ const state = globalThis.getExtensionState()
1250
+ return state.tabs.size
1251
+ })
1252
+ expect(tabCountBefore).toBe(0)
1202
1253
 
1203
- const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1254
+ const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
1204
1255
 
1205
- const pages = browser.contexts()[0].pages()
1206
- expect(pages.length).toBeGreaterThan(0)
1207
- expect(pages.length).toBe(1)
1256
+ const pages = browser.contexts()[0].pages()
1257
+ expect(pages.length).toBeGreaterThan(0)
1258
+ expect(pages.length).toBe(1)
1208
1259
 
1209
- const autoCreatedPage = pages[0]
1210
- expect(autoCreatedPage.url()).toBe('about:blank')
1260
+ const autoCreatedPage = pages[0]
1261
+ expect(autoCreatedPage.url()).toBe('about:blank')
1211
1262
 
1212
- const tabCountAfter = await serviceWorker.evaluate(() => {
1213
- const state = globalThis.getExtensionState()
1214
- return state.tabs.size
1215
- })
1216
- expect(tabCountAfter).toBe(1)
1263
+ const tabCountAfter = await serviceWorker.evaluate(() => {
1264
+ const state = globalThis.getExtensionState()
1265
+ return state.tabs.size
1266
+ })
1267
+ expect(tabCountAfter).toBe(1)
1217
1268
 
1218
- await autoCreatedPage.setContent('<h1>Auto-created page</h1>')
1219
- const title = await autoCreatedPage.locator('h1').textContent()
1220
- expect(title).toBe('Auto-created page')
1269
+ await autoCreatedPage.setContent('<h1>Auto-created page</h1>')
1270
+ const title = await autoCreatedPage.locator('h1').textContent()
1271
+ expect(title).toBe('Auto-created page')
1221
1272
 
1222
- await browser.close()
1223
- }, 60000)
1273
+ await browser.close()
1274
+ }, 60000)
1224
1275
 
1225
- it('should auto-create a page when MCP executes with no connected pages', async () => {
1226
- const browserContext = getBrowserContext()
1227
- const serviceWorker = await getExtensionServiceWorker(browserContext)
1276
+ it('should auto-create a page when MCP executes with no connected pages', async () => {
1277
+ const browserContext = getBrowserContext()
1278
+ const serviceWorker = await getExtensionServiceWorker(browserContext)
1228
1279
 
1229
- await serviceWorker.evaluate(async () => {
1230
- await globalThis.disconnectEverything()
1231
- })
1232
- await new Promise((r) => { setTimeout(r, 100) })
1280
+ await serviceWorker.evaluate(async () => {
1281
+ await globalThis.disconnectEverything()
1282
+ })
1283
+ await new Promise((r) => {
1284
+ setTimeout(r, 100)
1285
+ })
1233
1286
 
1234
- const tabCountBefore = await serviceWorker.evaluate(() => {
1235
- const state = globalThis.getExtensionState()
1236
- return state.tabs.size
1237
- })
1238
- expect(tabCountBefore).toBe(0)
1287
+ const tabCountBefore = await serviceWorker.evaluate(() => {
1288
+ const state = globalThis.getExtensionState()
1289
+ return state.tabs.size
1290
+ })
1291
+ expect(tabCountBefore).toBe(0)
1239
1292
 
1240
- const result = await client.callTool({
1241
- name: 'execute',
1242
- arguments: {
1243
- code: js`
1293
+ const result = await client.callTool({
1294
+ name: 'execute',
1295
+ arguments: {
1296
+ code: js`
1244
1297
  return { pageCount: context.pages().length, url: page.url() };
1245
1298
  `,
1246
- },
1247
- })
1299
+ },
1300
+ })
1248
1301
 
1249
- expect((result as any).isError).toBeFalsy()
1250
- const text = (result as any).content[0].text
1251
- expect(text).toContain('pageCount')
1252
- expect(text).toContain('about:blank')
1302
+ expect((result as any).isError).toBeFalsy()
1303
+ const text = (result as any).content[0].text
1304
+ expect(text).toContain('pageCount')
1305
+ expect(text).toContain('about:blank')
1253
1306
 
1254
- const tabCountAfter = await serviceWorker.evaluate(() => {
1255
- const state = globalThis.getExtensionState()
1256
- return state.tabs.size
1257
- })
1258
- expect(tabCountAfter).toBe(1)
1307
+ const tabCountAfter = await serviceWorker.evaluate(() => {
1308
+ const state = globalThis.getExtensionState()
1309
+ return state.tabs.size
1310
+ })
1311
+ expect(tabCountAfter).toBe(1)
1259
1312
 
1260
- await client.callTool({
1261
- name: 'execute',
1262
- arguments: {
1263
- code: js`
1313
+ await client.callTool({
1314
+ name: 'execute',
1315
+ arguments: {
1316
+ code: js`
1264
1317
  await page.close();
1265
1318
  return { remaining: context.pages().length };
1266
1319
  `,
1267
- },
1268
- })
1320
+ },
1321
+ })
1269
1322
 
1270
- await new Promise((r) => { setTimeout(r, 100) })
1323
+ await new Promise((r) => {
1324
+ setTimeout(r, 100)
1325
+ })
1271
1326
 
1272
- const afterCloseResult = await client.callTool({
1273
- name: 'execute',
1274
- arguments: {
1275
- code: js`
1327
+ const afterCloseResult = await client.callTool({
1328
+ name: 'execute',
1329
+ arguments: {
1330
+ code: js`
1276
1331
  return { pageCount: context.pages().length, url: page.url() };
1277
1332
  `,
1278
- },
1279
- })
1333
+ },
1334
+ })
1280
1335
 
1281
- expect((afterCloseResult as any).isError).toBeFalsy()
1282
- const afterCloseText = (afterCloseResult as any).content[0].text
1283
- expect(afterCloseText).toContain('pageCount')
1284
- expect(afterCloseText).toContain('about:blank')
1285
- }, 60000)
1336
+ expect((afterCloseResult as any).isError).toBeFalsy()
1337
+ const afterCloseText = (afterCloseResult as any).content[0].text
1338
+ expect(afterCloseText).toContain('pageCount')
1339
+ expect(afterCloseText).toContain('about:blank')
1340
+ }, 60000)
1286
1341
  })