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.
- package/dist/a11y-client.js +18 -8
- package/dist/aria-snapshot.d.ts +41 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +134 -55
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +5 -2
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/aria-snapshot.unit.test.js +83 -41
- package/dist/aria-snapshot.unit.test.js.map +1 -1
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
- package/dist/bippy.js +1 -1
- package/dist/cdp-log.d.ts +1 -1
- package/dist/cdp-log.d.ts.map +1 -1
- package/dist/cdp-log.js +1 -1
- package/dist/cdp-log.js.map +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +492 -298
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js.map +1 -1
- package/dist/cdp-types.d.ts.map +1 -1
- package/dist/cdp-types.js +7 -7
- package/dist/cdp-types.js.map +1 -1
- package/dist/clean-html.d.ts.map +1 -1
- package/dist/clean-html.js +4 -5
- package/dist/clean-html.js.map +1 -1
- package/dist/cli.js +45 -27
- package/dist/cli.js.map +1 -1
- package/dist/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +3 -1
- package/dist/create-logger.js.map +1 -1
- package/dist/debugger-examples-types.d.ts.map +1 -1
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +1 -3
- package/dist/debugger.js.map +1 -1
- package/dist/diff-utils.d.ts.map +1 -1
- package/dist/diff-utils.js +1 -4
- package/dist/diff-utils.js.map +1 -1
- package/dist/editor-api.md +12 -2
- package/dist/editor-examples.d.ts +1 -1
- package/dist/editor-examples.d.ts.map +1 -1
- package/dist/editor-examples.js +1 -1
- package/dist/editor-examples.js.map +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/executor.d.ts +26 -3
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +297 -64
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +38 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension-connection.test.js +139 -36
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ffmpeg.d.ts +148 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +523 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/ghost-browser.d.ts.map +1 -1
- package/dist/ghost-browser.js.map +1 -1
- package/dist/ghost-cursor-client.js +287 -0
- package/dist/ghost-cursor.d.ts +27 -0
- package/dist/ghost-cursor.d.ts.map +1 -0
- package/dist/ghost-cursor.js +63 -0
- package/dist/ghost-cursor.js.map +1 -0
- package/dist/htmlrewrite.d.ts.map +1 -1
- package/dist/htmlrewrite.js +17 -55
- package/dist/htmlrewrite.js.map +1 -1
- package/dist/htmlrewrite.test.js.map +1 -1
- package/dist/kill-port.d.ts.map +1 -1
- package/dist/kill-port.js +1 -3
- package/dist/kill-port.js.map +1 -1
- package/dist/locator-selector.test.d.ts +2 -0
- package/dist/locator-selector.test.d.ts.map +1 -0
- package/dist/locator-selector.test.js +96 -0
- package/dist/locator-selector.test.js.map +1 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -3
- package/dist/mcp.js.map +1 -1
- package/dist/on-mouse-action.test.d.ts +2 -0
- package/dist/on-mouse-action.test.d.ts.map +1 -0
- package/dist/on-mouse-action.test.js +155 -0
- package/dist/on-mouse-action.test.js.map +1 -0
- package/dist/page-markdown.js +4 -4
- package/dist/page-markdown.js.map +1 -1
- package/dist/prompt.md +450 -377
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/readability.js +16 -2
- package/dist/recording-ghost-cursor.d.ts +41 -0
- package/dist/recording-ghost-cursor.d.ts.map +1 -0
- package/dist/recording-ghost-cursor.js +79 -0
- package/dist/recording-ghost-cursor.js.map +1 -0
- package/dist/recording-relay.d.ts.map +1 -1
- package/dist/recording-relay.js +8 -8
- package/dist/recording-relay.js.map +1 -1
- package/dist/relay-client.d.ts +17 -4
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +45 -11
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +515 -26
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-navigation.test.d.ts.map +1 -1
- package/dist/relay-navigation.test.js +169 -31
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/relay-session.test.d.ts.map +1 -1
- package/dist/relay-session.test.js +113 -65
- package/dist/relay-session.test.js.map +1 -1
- package/dist/relay-state.d.ts +158 -0
- package/dist/relay-state.d.ts.map +1 -0
- package/dist/relay-state.js +306 -0
- package/dist/relay-state.js.map +1 -0
- package/dist/relay-state.test.d.ts +2 -0
- package/dist/relay-state.test.d.ts.map +1 -0
- package/dist/relay-state.test.js +472 -0
- package/dist/relay-state.test.js.map +1 -0
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js.map +1 -1
- package/dist/screen-recording.d.ts +66 -4
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +150 -13
- package/dist/screen-recording.js.map +1 -1
- package/dist/screen-recording.test.d.ts +2 -0
- package/dist/screen-recording.test.d.ts.map +1 -0
- package/dist/screen-recording.test.js +102 -0
- package/dist/screen-recording.test.js.map +1 -0
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +71 -28
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/start-relay-server.d.ts +1 -1
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +1 -1
- package/dist/start-relay-server.js.map +1 -1
- package/dist/styles-api.md +8 -1
- package/dist/styles-examples.d.ts +1 -1
- package/dist/styles-examples.d.ts.map +1 -1
- package/dist/styles-examples.js +1 -1
- package/dist/styles-examples.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +1 -3
- package/dist/styles.js.map +1 -1
- package/dist/test-declarations.d.ts.map +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +7 -5
- package/dist/test-utils.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/wait-for-page-load.d.ts.map +1 -1
- package/dist/wait-for-page-load.js +1 -1
- package/dist/wait-for-page-load.js.map +1 -1
- package/package.json +4 -3
- package/src/a11y-client.ts +5 -4
- package/src/aria-snapshot.test.ts +5 -2
- package/src/aria-snapshot.ts +306 -117
- package/src/aria-snapshot.unit.test.ts +199 -141
- package/src/aria-snapshots/github-interactive.txt +2 -0
- package/src/aria-snapshots/github-raw.txt +5 -1
- package/src/aria-snapshots/hackernews-interactive.txt +238 -241
- package/src/aria-snapshots/hackernews-raw.txt +265 -269
- package/src/assets/aria-labels-example.png +0 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
- package/src/cdp-log.ts +4 -1
- package/src/cdp-relay.ts +1059 -737
- package/src/cdp-session.ts +12 -3
- package/src/cdp-types.ts +51 -51
- package/src/clean-html.ts +4 -5
- package/src/cli.ts +82 -55
- package/src/create-logger.ts +5 -3
- package/src/debugger-examples-types.ts +4 -1
- package/src/debugger.ts +1 -5
- package/src/diff-utils.ts +2 -5
- package/src/editor-examples.ts +11 -1
- package/src/editor.ts +10 -2
- package/src/executor.ts +374 -73
- package/src/executor.unit.test.ts +48 -1
- package/src/extension-connection.test.ts +612 -488
- package/src/ffmpeg.ts +769 -0
- package/src/ghost-browser.ts +4 -6
- package/src/ghost-cursor-client.ts +369 -0
- package/src/ghost-cursor.ts +110 -0
- package/src/htmlrewrite.test.ts +6 -2
- package/src/htmlrewrite.ts +348 -386
- package/src/kill-port.ts +1 -3
- package/src/locator-selector.test.ts +115 -0
- package/src/mcp-client.ts +1 -1
- package/src/mcp.ts +21 -15
- package/src/on-mouse-action.test.ts +196 -0
- package/src/page-markdown.ts +7 -7
- package/src/protocol.ts +73 -57
- package/src/recording-ghost-cursor.ts +113 -0
- package/src/recording-relay.ts +20 -12
- package/src/relay-client.ts +85 -18
- package/src/relay-core.test.ts +1117 -578
- package/src/relay-navigation.test.ts +648 -483
- package/src/relay-session.test.ts +984 -929
- package/src/relay-state.test.ts +570 -0
- package/src/relay-state.ts +497 -0
- package/src/resource.md +21 -49
- package/src/scoped-fs.ts +9 -3
- package/src/screen-recording.test.ts +111 -0
- package/src/screen-recording.ts +256 -31
- package/src/skill.md +476 -396
- package/src/snapshot-tools.test.ts +580 -528
- package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
- package/src/start-relay-server.ts +14 -11
- package/src/styles-examples.ts +8 -1
- package/src/styles.ts +20 -21
- package/src/test-declarations.ts +6 -6
- package/src/test-utils.ts +104 -91
- package/src/utils.ts +2 -1
- 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 {
|
|
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
|
-
|
|
26
|
+
let testCtx: TestContext | null = null
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-cdp-test-', toggleExtension: true })
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
66
|
+
await dbg.enable()
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
expect(dbg.isPaused()).toBe(false)
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
71
|
+
cdpSession.on('Debugger.paused', () => {
|
|
72
|
+
resolve()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
await Promise.race([
|
|
86
|
+
pausedPromise,
|
|
87
|
+
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
|
|
88
|
+
])
|
|
82
89
|
|
|
83
|
-
|
|
90
|
+
expect(dbg.isPaused()).toBe(true)
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
const location = await dbg.getLocation()
|
|
93
|
+
expect(location.callstack[0].functionName).toBe('testFunction')
|
|
94
|
+
expect(location.sourceContext).toContain('debugger')
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
await dbg.resume()
|
|
108
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
109
|
+
expect(dbg.isPaused()).toBe(false)
|
|
110
|
+
await evalPromise
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
await cdpSession.detach()
|
|
113
|
+
await page.close()
|
|
114
|
+
}, 60000)
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
182
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
183
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
166
184
|
|
|
167
|
-
|
|
185
|
+
await dbg.enable()
|
|
168
186
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
192
|
+
await cdpSession.detach()
|
|
193
|
+
await browser.close()
|
|
194
|
+
await page.close()
|
|
195
|
+
}, 60000)
|
|
178
196
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
197
|
+
it('should manage breakpoints with Debugger class', async () => {
|
|
198
|
+
const browserContext = getBrowserContext()
|
|
199
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
182
200
|
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
210
|
+
await page.bringToFront()
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
await serviceWorker.evaluate(async () => {
|
|
213
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
214
|
+
})
|
|
215
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
198
216
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
217
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
218
|
+
const cdpSession = await getCDPSessionForPage({ page })
|
|
219
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
202
220
|
|
|
203
|
-
|
|
221
|
+
await dbg.enable()
|
|
204
222
|
|
|
205
|
-
|
|
223
|
+
expect(dbg.listBreakpoints()).toHaveLength(0)
|
|
206
224
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
234
|
+
await dbg.deleteBreakpoint({ breakpointId: bpId })
|
|
235
|
+
expect(dbg.listBreakpoints()).toHaveLength(0)
|
|
218
236
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
237
|
+
await cdpSession.detach()
|
|
238
|
+
await page.close()
|
|
239
|
+
}, 60000)
|
|
222
240
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
241
|
+
it('should step through code with Debugger class', async () => {
|
|
242
|
+
const browserContext = getBrowserContext()
|
|
243
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
226
244
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
245
|
+
const page = await browserContext.newPage()
|
|
246
|
+
await page.goto('https://example.com/')
|
|
247
|
+
await page.bringToFront()
|
|
230
248
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
249
|
+
await serviceWorker.evaluate(async () => {
|
|
250
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
251
|
+
})
|
|
252
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
235
253
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
255
|
+
const cdpSession = await getCDPSessionForPage({ page })
|
|
256
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
239
257
|
|
|
240
|
-
|
|
258
|
+
await dbg.enable()
|
|
241
259
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
261
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
262
|
+
})
|
|
245
263
|
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
260
|
-
|
|
277
|
+
await pausedPromise
|
|
278
|
+
expect(dbg.isPaused()).toBe(true)
|
|
261
279
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
const stepOverPromise = new Promise<void>((resolve) => {
|
|
286
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
287
|
+
})
|
|
288
|
+
await dbg.stepOver()
|
|
289
|
+
await stepOverPromise
|
|
272
290
|
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
const location2 = await dbg.getLocation()
|
|
292
|
+
expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber)
|
|
275
293
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
294
|
+
const stepOutPromise = new Promise<void>((resolve) => {
|
|
295
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
296
|
+
})
|
|
297
|
+
await dbg.stepOut()
|
|
298
|
+
await stepOutPromise
|
|
281
299
|
|
|
282
|
-
|
|
283
|
-
|
|
300
|
+
const location3 = await dbg.getLocation()
|
|
301
|
+
expect(location3.callstack[0].functionName).toBe('outer')
|
|
284
302
|
|
|
285
|
-
|
|
286
|
-
|
|
303
|
+
await dbg.resume()
|
|
304
|
+
await evalPromise
|
|
287
305
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
306
|
+
await cdpSession.detach()
|
|
307
|
+
await page.close()
|
|
308
|
+
}, 60000)
|
|
291
309
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
it('should profile JavaScript execution using CDP Profiler', async () => {
|
|
311
|
+
const browserContext = getBrowserContext()
|
|
312
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
295
313
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
await serviceWorker.evaluate(async () => {
|
|
320
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
321
|
+
})
|
|
322
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
327
|
-
|
|
344
|
+
const stopResult = await cdpSession.send('Profiler.stop')
|
|
345
|
+
const profile = stopResult.profile
|
|
328
346
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
356
|
+
await cdpSession.send('Profiler.disable')
|
|
357
|
+
await cdpSession.detach()
|
|
358
|
+
await page.close()
|
|
359
|
+
}, 60000)
|
|
342
360
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
361
|
+
it('should update Target.getTargets URL after page navigation', async () => {
|
|
362
|
+
const browserContext = getBrowserContext()
|
|
363
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
346
364
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
365
|
+
await serviceWorker.evaluate(async () => {
|
|
366
|
+
await globalThis.disconnectEverything()
|
|
367
|
+
})
|
|
350
368
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
369
|
+
const page = await browserContext.newPage()
|
|
370
|
+
await page.goto('https://example.com/')
|
|
371
|
+
await page.bringToFront()
|
|
354
372
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
373
|
+
await serviceWorker.evaluate(async () => {
|
|
374
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
375
|
+
})
|
|
376
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
359
377
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
365
|
-
|
|
385
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
386
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
366
387
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
372
|
-
|
|
392
|
+
await cdpPage!.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
|
|
393
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
373
394
|
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
const afterNavTargets = await cdpSession.send('Target.getTargets')
|
|
396
|
+
const allPageTargets = afterNavTargets.targetInfos.filter((t) => t.type === 'page')
|
|
376
397
|
|
|
377
|
-
|
|
378
|
-
|
|
398
|
+
const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
|
|
399
|
+
expect(aboutBlankTargets).toHaveLength(0)
|
|
379
400
|
|
|
380
|
-
|
|
381
|
-
|
|
401
|
+
const exampleComTargets = allPageTargets.filter((t) => t.url.includes('example.com'))
|
|
402
|
+
expect(exampleComTargets).toHaveLength(0)
|
|
382
403
|
|
|
383
|
-
|
|
384
|
-
|
|
404
|
+
const exampleOrgTargets = allPageTargets.filter((t) => t.url.includes('example.org'))
|
|
405
|
+
expect(exampleOrgTargets).toHaveLength(1)
|
|
385
406
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
407
|
+
await cdpSession.detach()
|
|
408
|
+
await browser.close()
|
|
409
|
+
await page.close()
|
|
410
|
+
}, 60000)
|
|
390
411
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
416
|
+
await serviceWorker.evaluate(async () => {
|
|
417
|
+
await globalThis.disconnectEverything()
|
|
418
|
+
})
|
|
398
419
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
434
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
414
435
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
420
|
-
|
|
443
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
444
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
421
445
|
|
|
422
|
-
|
|
423
|
-
|
|
446
|
+
const { targetInfos } = await cdpSession.send('Target.getTargets')
|
|
447
|
+
const allPageTargets = targetInfos.filter((t) => t.type === 'page')
|
|
424
448
|
|
|
425
|
-
|
|
426
|
-
|
|
449
|
+
const aboutBlankTargets = allPageTargets.filter((t) => t.url === 'about:blank')
|
|
450
|
+
expect(aboutBlankTargets).toHaveLength(0)
|
|
427
451
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
469
|
+
await cdpSession.detach()
|
|
470
|
+
await browser.close()
|
|
471
|
+
await page1.close()
|
|
472
|
+
await page2.close()
|
|
473
|
+
}, 60000)
|
|
450
474
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
475
|
+
it('should create CDP session for page after navigation', async () => {
|
|
476
|
+
const browserContext = getBrowserContext()
|
|
477
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
454
478
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
479
|
+
const page = await browserContext.newPage()
|
|
480
|
+
await page.goto('https://example.com/')
|
|
481
|
+
await page.bringToFront()
|
|
458
482
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
483
|
+
await serviceWorker.evaluate(async () => {
|
|
484
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
485
|
+
})
|
|
486
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
463
487
|
|
|
464
|
-
|
|
465
|
-
|
|
488
|
+
await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
|
|
489
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
466
490
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
472
|
-
|
|
498
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
499
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
473
500
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
507
|
+
await cdpSession.detach()
|
|
508
|
+
await browser.close()
|
|
509
|
+
await page.close()
|
|
510
|
+
}, 60000)
|
|
484
511
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
516
|
+
const page = await browserContext.newPage()
|
|
517
|
+
const initialUrl = 'https://example.com/'
|
|
518
|
+
await page.goto(initialUrl)
|
|
519
|
+
await page.bringToFront()
|
|
493
520
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
521
|
+
await serviceWorker.evaluate(async () => {
|
|
522
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
523
|
+
})
|
|
524
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
498
525
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
504
|
-
|
|
533
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
534
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
505
535
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
513
|
-
|
|
542
|
+
const newUrl = 'https://example.org/'
|
|
543
|
+
await cdpPage!.goto(newUrl, { waitUntil: 'domcontentloaded' })
|
|
514
544
|
|
|
515
|
-
|
|
545
|
+
expect(cdpPage!.url()).toBe(newUrl)
|
|
516
546
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
547
|
+
const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
|
|
548
|
+
expect(layoutMetrics.cssVisualViewport).toBeDefined()
|
|
549
|
+
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
|
|
520
550
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
563
|
+
await cdpSession.detach()
|
|
564
|
+
await browser.close()
|
|
565
|
+
await page.close()
|
|
566
|
+
}, 60000)
|
|
537
567
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
568
|
+
it('should pause on all exceptions with setPauseOnExceptions', async () => {
|
|
569
|
+
const browserContext = getBrowserContext()
|
|
570
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
541
571
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
572
|
+
const page = await browserContext.newPage()
|
|
573
|
+
await page.goto('https://example.com/')
|
|
574
|
+
await page.bringToFront()
|
|
545
575
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
576
|
+
await serviceWorker.evaluate(async () => {
|
|
577
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
578
|
+
})
|
|
579
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
550
580
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
588
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
589
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
590
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
558
591
|
|
|
559
|
-
|
|
560
|
-
|
|
592
|
+
await dbg.enable()
|
|
593
|
+
await dbg.setPauseOnExceptions({ state: 'all' })
|
|
561
594
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
595
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
596
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
597
|
+
})
|
|
565
598
|
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
609
|
+
await Promise.race([
|
|
610
|
+
pausedPromise,
|
|
611
|
+
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000)),
|
|
612
|
+
])
|
|
580
613
|
|
|
581
|
-
|
|
614
|
+
expect(dbg.isPaused()).toBe(true)
|
|
582
615
|
|
|
583
|
-
|
|
584
|
-
|
|
616
|
+
const location = await dbg.getLocation()
|
|
617
|
+
expect(location.sourceContext).toContain('throw')
|
|
585
618
|
|
|
586
|
-
|
|
587
|
-
|
|
619
|
+
await dbg.resume()
|
|
620
|
+
await evalPromise
|
|
588
621
|
|
|
589
|
-
|
|
622
|
+
await dbg.setPauseOnExceptions({ state: 'none' })
|
|
590
623
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
624
|
+
await cdpSession.detach()
|
|
625
|
+
await browser.close()
|
|
626
|
+
await page.close()
|
|
627
|
+
}, 60000)
|
|
595
628
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
601
|
-
|
|
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
|
-
|
|
654
|
+
await page.bringToFront()
|
|
622
655
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
672
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
673
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
674
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
642
675
|
|
|
643
|
-
|
|
676
|
+
await dbg.enable()
|
|
644
677
|
|
|
645
|
-
|
|
646
|
-
|
|
678
|
+
const globalVars = await dbg.inspectGlobalVariables()
|
|
679
|
+
expect(globalVars).toMatchInlineSnapshot(`
|
|
647
680
|
[
|
|
648
681
|
"GLOBAL_CONFIG",
|
|
649
682
|
]
|
|
650
683
|
`)
|
|
651
684
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
685
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
686
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
687
|
+
})
|
|
655
688
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
662
|
-
|
|
694
|
+
await pausedPromise
|
|
695
|
+
expect(dbg.isPaused()).toBe(true)
|
|
663
696
|
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
|
|
712
|
+
await cdpSession.detach()
|
|
713
|
+
await browser.close()
|
|
714
|
+
await page.close()
|
|
715
|
+
}, 60000)
|
|
691
716
|
|
|
692
|
-
|
|
693
|
-
|
|
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
|
-
|
|
702
|
-
|
|
703
|
-
|
|
721
|
+
const page = await browserContext.newPage()
|
|
722
|
+
await page.goto('https://example.com/')
|
|
723
|
+
await page.bringToFront()
|
|
704
724
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
748
|
+
await cdpPage!.locator('h1').click()
|
|
713
749
|
|
|
714
|
-
|
|
715
|
-
|
|
750
|
+
const clickedAt = await cdpPage!.evaluate(() => (window as any).clickedAt)
|
|
751
|
+
console.log('Clicked at:', clickedAt)
|
|
716
752
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
753
|
+
expect(clickedAt).toBeDefined()
|
|
754
|
+
expect(clickedAt.x).toBeGreaterThan(0)
|
|
755
|
+
expect(clickedAt.y).toBeGreaterThan(0)
|
|
720
756
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
757
|
+
await browser.close()
|
|
758
|
+
await page.close()
|
|
759
|
+
}, 60000)
|
|
724
760
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
765
|
+
const page = await browserContext.newPage()
|
|
766
|
+
await page.goto('https://example.com/')
|
|
767
|
+
await page.bringToFront()
|
|
732
768
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
769
|
+
await serviceWorker.evaluate(async () => {
|
|
770
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
771
|
+
})
|
|
772
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
737
773
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
781
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
782
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
783
|
+
const editor = new Editor({ cdp: cdpSession })
|
|
745
784
|
|
|
746
|
-
|
|
785
|
+
await editor.enable()
|
|
747
786
|
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
813
|
+
const consoleLogs: string[] = []
|
|
814
|
+
cdpPage!.on('console', (msg) => {
|
|
815
|
+
consoleLogs.push(msg.text())
|
|
816
|
+
})
|
|
778
817
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
818
|
+
await cdpPage!.evaluate(() => {
|
|
819
|
+
;(window as any).greetUser('World')
|
|
820
|
+
})
|
|
821
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
783
822
|
|
|
784
|
-
|
|
785
|
-
|
|
823
|
+
expect(consoleLogs).toContain('Hello, World')
|
|
824
|
+
expect(consoleLogs).toContain('EDITOR_TEST_MARKER')
|
|
786
825
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
826
|
+
await cdpSession.detach()
|
|
827
|
+
await browser.close()
|
|
828
|
+
await page.close()
|
|
829
|
+
}, 60000)
|
|
791
830
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
831
|
+
it('editor can list, read, and edit CSS stylesheets', async () => {
|
|
832
|
+
const browserContext = getBrowserContext()
|
|
833
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
795
834
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
835
|
+
const page = await browserContext.newPage()
|
|
836
|
+
await page.goto('https://example.com/')
|
|
837
|
+
await page.bringToFront()
|
|
799
838
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
839
|
+
await serviceWorker.evaluate(async () => {
|
|
840
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
841
|
+
})
|
|
842
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
804
843
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
851
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
852
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
853
|
+
const editor = new Editor({ cdp: cdpSession })
|
|
812
854
|
|
|
813
|
-
|
|
855
|
+
await editor.enable()
|
|
814
856
|
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
904
|
+
await cdpSession.detach()
|
|
905
|
+
await browser.close()
|
|
906
|
+
await page.close()
|
|
907
|
+
}, 60000)
|
|
866
908
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
872
|
-
|
|
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
|
-
|
|
933
|
+
await page.bringToFront()
|
|
892
934
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
935
|
+
await serviceWorker.evaluate(async () => {
|
|
936
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
937
|
+
})
|
|
938
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
897
939
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
945
|
+
const btn = cdpPage!.locator('#react-btn')
|
|
946
|
+
const btnCount = await btn.count()
|
|
947
|
+
expect(btnCount).toBe(1)
|
|
906
948
|
|
|
907
|
-
|
|
908
|
-
|
|
949
|
+
const hasBippyBefore = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
|
950
|
+
expect(hasBippyBefore).toBe(false)
|
|
909
951
|
|
|
910
|
-
|
|
911
|
-
|
|
952
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
953
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage! })
|
|
912
954
|
|
|
913
|
-
|
|
914
|
-
|
|
955
|
+
const { getReactSource } = await import('./react-source.js')
|
|
956
|
+
const source = await getReactSource({ locator: btn, cdp: cdpSession })
|
|
915
957
|
|
|
916
|
-
|
|
917
|
-
|
|
958
|
+
const hasBippyAfter = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
|
959
|
+
expect(hasBippyAfter).toBe(true)
|
|
918
960
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
941
|
-
|
|
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
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
-
|
|
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
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
995
|
+
beforeAll(async () => {
|
|
996
|
+
testCtx = await setupTestContext({ port: TEST_PORT, tempDirPrefix: 'pw-sw-test-', toggleExtension: true })
|
|
997
|
+
}, 600000)
|
|
966
998
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
999
|
+
afterAll(async () => {
|
|
1000
|
+
await cleanupTestContext(testCtx)
|
|
1001
|
+
testCtx = null
|
|
1002
|
+
})
|
|
970
1003
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1004
|
+
const getBrowserContext = () => {
|
|
1005
|
+
if (!testCtx?.browserContext) throw new Error('Browser not initialized')
|
|
1006
|
+
return testCtx.browserContext
|
|
1007
|
+
}
|
|
974
1008
|
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
981
|
-
|
|
1013
|
+
const page = await browserContext.newPage()
|
|
1014
|
+
await page.goto('https://web.dev/', { waitUntil: 'load' })
|
|
1015
|
+
await page.bringToFront()
|
|
982
1016
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
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
|
-
|
|
993
|
-
|
|
1022
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1023
|
+
const context = browser.contexts()[0]
|
|
994
1024
|
|
|
995
|
-
|
|
996
|
-
expect(title).toBeTruthy()
|
|
1025
|
+
const pages = context.pages()
|
|
997
1026
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
1004
|
-
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1034
|
+
const targetPage = pages.find((p) => p.url().includes('web.dev'))
|
|
1035
|
+
expect(targetPage).toBeDefined()
|
|
1005
1036
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
await page.bringToFront()
|
|
1037
|
+
const title = await targetPage!.title()
|
|
1038
|
+
expect(title).toBeTruthy()
|
|
1009
1039
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
await new Promise(r => setTimeout(r, 100))
|
|
1040
|
+
await safeCloseCDPBrowser(browser)
|
|
1041
|
+
await page.close()
|
|
1042
|
+
}, 60000)
|
|
1014
1043
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
-
|
|
1020
|
-
|
|
1048
|
+
const page = await browserContext.newPage()
|
|
1049
|
+
await page.goto('https://example.com/')
|
|
1050
|
+
await page.bringToFront()
|
|
1021
1051
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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
|
-
|
|
1078
|
+
const body = await response.text()
|
|
1034
1079
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1080
|
+
expect(body).toBeDefined()
|
|
1081
|
+
expect(body).toContain('Example Domain')
|
|
1082
|
+
expect(body).toContain('</html>')
|
|
1038
1083
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1084
|
+
await cdpSession.detach()
|
|
1085
|
+
await browser.close()
|
|
1086
|
+
await page.close()
|
|
1087
|
+
}, 60000)
|
|
1043
1088
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
-
|
|
1254
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1204
1255
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1256
|
+
const pages = browser.contexts()[0].pages()
|
|
1257
|
+
expect(pages.length).toBeGreaterThan(0)
|
|
1258
|
+
expect(pages.length).toBe(1)
|
|
1208
1259
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1260
|
+
const autoCreatedPage = pages[0]
|
|
1261
|
+
expect(autoCreatedPage.url()).toBe('about:blank')
|
|
1211
1262
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
1273
|
+
await browser.close()
|
|
1274
|
+
}, 60000)
|
|
1224
1275
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1280
|
+
await serviceWorker.evaluate(async () => {
|
|
1281
|
+
await globalThis.disconnectEverything()
|
|
1282
|
+
})
|
|
1283
|
+
await new Promise((r) => {
|
|
1284
|
+
setTimeout(r, 100)
|
|
1285
|
+
})
|
|
1233
1286
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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
|
-
|
|
1323
|
+
await new Promise((r) => {
|
|
1324
|
+
setTimeout(r, 100)
|
|
1325
|
+
})
|
|
1271
1326
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
})
|