playwriter 0.0.26 → 0.0.29
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/bin.js +1 -1
- package/dist/bippy.js +966 -0
- package/dist/{extension/cdp-relay.d.ts → cdp-relay.d.ts} +3 -2
- package/dist/cdp-relay.d.ts.map +1 -0
- package/dist/{extension/cdp-relay.js → cdp-relay.js} +101 -3
- package/dist/cdp-relay.js.map +1 -0
- package/dist/cdp-session.d.ts +1 -1
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js +4 -4
- package/dist/cdp-session.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +71 -0
- package/dist/cli.js.map +1 -0
- package/dist/debugger-examples-types.d.ts +18 -0
- package/dist/debugger-examples-types.d.ts.map +1 -0
- package/dist/debugger-examples-types.js +2 -0
- package/dist/debugger-examples-types.js.map +1 -0
- package/dist/debugger-examples.d.ts +6 -0
- package/dist/debugger-examples.d.ts.map +1 -0
- package/dist/debugger-examples.js +53 -0
- package/dist/debugger-examples.js.map +1 -0
- package/dist/debugger-examples.ts +66 -0
- package/dist/debugger.d.ts +380 -0
- package/dist/debugger.d.ts.map +1 -0
- package/dist/debugger.js +631 -0
- package/dist/debugger.js.map +1 -0
- package/dist/editor-examples.d.ts +11 -0
- package/dist/editor-examples.d.ts.map +1 -0
- package/dist/editor-examples.js +124 -0
- package/dist/editor-examples.js.map +1 -0
- package/dist/editor.d.ts +203 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +335 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +5 -1
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +13 -9
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp.d.ts +4 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +170 -29
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.d.ts.map +1 -1
- package/dist/mcp.test.js +886 -182
- package/dist/mcp.test.js.map +1 -1
- package/dist/prompt.md +86 -6
- package/dist/{extension/protocol.d.ts → protocol.d.ts} +1 -1
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react-source.d.ts +13 -0
- package/dist/react-source.d.ts.map +1 -0
- package/dist/react-source.js +66 -0
- package/dist/react-source.js.map +1 -0
- package/dist/selector-generator.js +7065 -18
- package/dist/start-relay-server.d.ts +4 -2
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +3 -3
- package/dist/start-relay-server.js.map +1 -1
- package/dist/styles.d.ts +27 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +232 -0
- package/dist/styles.js.map +1 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +7 -3
- 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 +3 -2
- package/dist/wait-for-page-load.js.map +1 -1
- package/package.json +4 -2
- package/src/{extension/cdp-relay.ts → cdp-relay.ts} +109 -5
- package/src/cdp-session.ts +4 -4
- package/src/cdp-timing.md +128 -0
- package/src/cli.ts +85 -0
- package/src/debugger-examples-types.ts +10 -0
- package/src/debugger-examples.ts +66 -0
- package/src/debugger.ts +711 -0
- package/src/editor-examples.ts +148 -0
- package/src/editor.ts +389 -0
- package/src/index.ts +1 -1
- package/src/mcp-client.ts +14 -9
- package/src/mcp.test.ts +1053 -196
- package/src/mcp.ts +195 -30
- package/src/prompt.md +86 -6
- package/src/{extension/protocol.ts → protocol.ts} +1 -1
- package/src/react-source.ts +92 -0
- package/src/snapshots/shadcn-ui-accessibility.md +57 -57
- package/src/start-relay-server.ts +3 -3
- package/src/styles.ts +343 -0
- package/src/utils.ts +8 -3
- package/src/wait-for-page-load.ts +3 -2
- package/dist/extension/cdp-relay.d.ts.map +0 -1
- package/dist/extension/cdp-relay.js.map +0 -1
- package/dist/extension/protocol.d.ts.map +0 -1
- package/dist/extension/protocol.js.map +0 -1
- /package/dist/{extension/protocol.js → protocol.js} +0 -0
package/src/mcp.test.ts
CHANGED
|
@@ -11,7 +11,9 @@ import type { ExtensionState } from 'mcp-extension/src/types.js'
|
|
|
11
11
|
import type { Protocol } from 'devtools-protocol'
|
|
12
12
|
import { imageSize } from 'image-size'
|
|
13
13
|
import { getCDPSessionForPage } from './cdp-session.js'
|
|
14
|
-
import {
|
|
14
|
+
import { Debugger } from './debugger.js'
|
|
15
|
+
import { Editor } from './editor.js'
|
|
16
|
+
import { startPlayWriterCDPRelayServer, type RelayServer } from './cdp-relay.js'
|
|
15
17
|
import { createFileLogger } from './create-logger.js'
|
|
16
18
|
import type { CDPCommand } from './cdp-types.js'
|
|
17
19
|
import { killPortProcess } from 'kill-port-process'
|
|
@@ -19,6 +21,7 @@ import { killPortProcess } from 'kill-port-process'
|
|
|
19
21
|
declare const window: any
|
|
20
22
|
declare const document: any
|
|
21
23
|
|
|
24
|
+
const TEST_PORT = 19987
|
|
22
25
|
|
|
23
26
|
const execAsync = promisify(exec)
|
|
24
27
|
|
|
@@ -66,15 +69,15 @@ interface TestContext {
|
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
async function setupTestContext({ tempDirPrefix }: { tempDirPrefix: string }): Promise<TestContext> {
|
|
69
|
-
await killProcessOnPort(
|
|
72
|
+
await killProcessOnPort(TEST_PORT)
|
|
70
73
|
|
|
71
74
|
console.log('Building extension...')
|
|
72
|
-
await execAsync(
|
|
75
|
+
await execAsync(`TESTING=1 PLAYWRITER_PORT=${TEST_PORT} pnpm build`, { cwd: '../extension' })
|
|
73
76
|
console.log('Extension built')
|
|
74
77
|
|
|
75
78
|
const localLogPath = path.join(process.cwd(), 'relay-server.log')
|
|
76
79
|
const logger = createFileLogger({ logFilePath: localLogPath })
|
|
77
|
-
const relayServer = await startPlayWriterCDPRelayServer({ port:
|
|
80
|
+
const relayServer = await startPlayWriterCDPRelayServer({ port: TEST_PORT, logger })
|
|
78
81
|
|
|
79
82
|
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix))
|
|
80
83
|
const extensionPath = path.resolve('../extension/dist')
|
|
@@ -135,7 +138,7 @@ describe('MCP Server Tests', () => {
|
|
|
135
138
|
beforeAll(async () => {
|
|
136
139
|
testCtx = await setupTestContext({ tempDirPrefix: 'pw-test-' })
|
|
137
140
|
|
|
138
|
-
const result = await createMCPClient()
|
|
141
|
+
const result = await createMCPClient({ port: TEST_PORT })
|
|
139
142
|
client = result.client
|
|
140
143
|
cleanup = result.cleanup
|
|
141
144
|
}, 600000)
|
|
@@ -162,9 +165,9 @@ describe('MCP Server Tests', () => {
|
|
|
162
165
|
await serviceWorker.evaluate(async () => {
|
|
163
166
|
await globalThis.toggleExtensionForActiveTab()
|
|
164
167
|
})
|
|
165
|
-
await new Promise(r => setTimeout(r,
|
|
168
|
+
await new Promise(r => setTimeout(r, 100))
|
|
166
169
|
|
|
167
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
170
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
168
171
|
const cdpPage = browser.contexts()[0].pages().find(p => {
|
|
169
172
|
return p.url().startsWith('about:')
|
|
170
173
|
})
|
|
@@ -199,7 +202,7 @@ describe('MCP Server Tests', () => {
|
|
|
199
202
|
name: 'execute',
|
|
200
203
|
arguments: {
|
|
201
204
|
code: js`
|
|
202
|
-
await state.page.goto('https://
|
|
205
|
+
await state.page.goto('https://example.com');
|
|
203
206
|
const title = await state.page.title();
|
|
204
207
|
console.log('Page title:', title);
|
|
205
208
|
return { url: state.page.url(), title };
|
|
@@ -210,12 +213,12 @@ describe('MCP Server Tests', () => {
|
|
|
210
213
|
[
|
|
211
214
|
{
|
|
212
215
|
"text": "Console output:
|
|
213
|
-
[log] Page title:
|
|
216
|
+
[log] Page title: Example Domain
|
|
214
217
|
|
|
215
218
|
Return value:
|
|
216
219
|
{
|
|
217
|
-
"url": "https://
|
|
218
|
-
"title": "
|
|
220
|
+
\"url\": \"https://example.com/\",
|
|
221
|
+
\"title\": \"Example Domain\"
|
|
219
222
|
}",
|
|
220
223
|
"type": "text",
|
|
221
224
|
},
|
|
@@ -289,7 +292,7 @@ describe('MCP Server Tests', () => {
|
|
|
289
292
|
name: 'execute',
|
|
290
293
|
arguments: {
|
|
291
294
|
code: js`
|
|
292
|
-
await state.page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: '
|
|
295
|
+
await state.page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: 'domcontentloaded' });
|
|
293
296
|
const snapshot = await state.page._snapshotForAI();
|
|
294
297
|
return snapshot;
|
|
295
298
|
`,
|
|
@@ -325,7 +328,7 @@ describe('MCP Server Tests', () => {
|
|
|
325
328
|
name: 'execute',
|
|
326
329
|
arguments: {
|
|
327
330
|
code: js`
|
|
328
|
-
await state.page.goto('https://ui.shadcn.com/', { waitUntil: '
|
|
331
|
+
await state.page.goto('https://ui.shadcn.com/', { waitUntil: 'domcontentloaded' });
|
|
329
332
|
const snapshot = await state.page._snapshotForAI();
|
|
330
333
|
return snapshot;
|
|
331
334
|
`,
|
|
@@ -381,7 +384,7 @@ describe('MCP Server Tests', () => {
|
|
|
381
384
|
|
|
382
385
|
// 3. Verify we can connect via direct CDP and see the page
|
|
383
386
|
|
|
384
|
-
let directBrowser = await chromium.connectOverCDP(getCdpUrl())
|
|
387
|
+
let directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
385
388
|
let contexts = directBrowser.contexts()
|
|
386
389
|
let pages = contexts[0].pages()
|
|
387
390
|
|
|
@@ -407,7 +410,7 @@ describe('MCP Server Tests', () => {
|
|
|
407
410
|
// connecting to relay will succeed, but listing pages should NOT show our page
|
|
408
411
|
|
|
409
412
|
// Connect to relay again
|
|
410
|
-
directBrowser = await chromium.connectOverCDP(getCdpUrl())
|
|
413
|
+
directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
411
414
|
contexts = directBrowser.contexts()
|
|
412
415
|
pages = contexts[0].pages()
|
|
413
416
|
|
|
@@ -425,14 +428,14 @@ describe('MCP Server Tests', () => {
|
|
|
425
428
|
|
|
426
429
|
// 7. Verify page is back
|
|
427
430
|
|
|
428
|
-
directBrowser = await chromium.connectOverCDP(getCdpUrl())
|
|
431
|
+
directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
429
432
|
// Wait a bit for targets to populate
|
|
430
|
-
await new Promise(r => setTimeout(r,
|
|
433
|
+
await new Promise(r => setTimeout(r, 100))
|
|
431
434
|
|
|
432
435
|
contexts = directBrowser.contexts()
|
|
433
436
|
// pages() might need a moment if target attached event comes in
|
|
434
437
|
if (contexts[0].pages().length === 0) {
|
|
435
|
-
await new Promise(r => setTimeout(r,
|
|
438
|
+
await new Promise(r => setTimeout(r, 100))
|
|
436
439
|
}
|
|
437
440
|
pages = contexts[0].pages()
|
|
438
441
|
|
|
@@ -453,9 +456,9 @@ describe('MCP Server Tests', () => {
|
|
|
453
456
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
454
457
|
|
|
455
458
|
// Connect once
|
|
456
|
-
const directBrowser = await chromium.connectOverCDP(getCdpUrl())
|
|
459
|
+
const directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
457
460
|
// Wait a bit for connection and initial target discovery
|
|
458
|
-
await new Promise(r => setTimeout(r,
|
|
461
|
+
await new Promise(r => setTimeout(r, 100))
|
|
459
462
|
|
|
460
463
|
// 1. Create a new page
|
|
461
464
|
const page = await browserContext.newPage()
|
|
@@ -535,7 +538,7 @@ describe('MCP Server Tests', () => {
|
|
|
535
538
|
})
|
|
536
539
|
|
|
537
540
|
// 3. Connect via CDP
|
|
538
|
-
const cdpUrl = getCdpUrl()
|
|
541
|
+
const cdpUrl = getCdpUrl({ port: TEST_PORT })
|
|
539
542
|
const directBrowser = await chromium.connectOverCDP(cdpUrl)
|
|
540
543
|
const connectedPage = directBrowser.contexts()[0].pages().find(p => p.url() === initialUrl)
|
|
541
544
|
expect(connectedPage).toBeDefined()
|
|
@@ -547,19 +550,19 @@ describe('MCP Server Tests', () => {
|
|
|
547
550
|
// We use a loop to check if it's still connected because reload might cause temporary disconnect/reconnect events
|
|
548
551
|
// that Playwright handles natively if the session ID stays valid.
|
|
549
552
|
await connectedPage?.reload()
|
|
550
|
-
await connectedPage?.waitForLoadState('
|
|
553
|
+
await connectedPage?.waitForLoadState('domcontentloaded')
|
|
551
554
|
expect(await connectedPage?.title()).toBe('Example Domain')
|
|
552
555
|
|
|
553
556
|
// Verify execution after reload
|
|
554
557
|
expect(await connectedPage?.evaluate(() => 2 + 2)).toBe(4)
|
|
555
558
|
|
|
556
559
|
// 5. Navigate to new URL
|
|
557
|
-
const newUrl = 'https://
|
|
560
|
+
const newUrl = 'https://example.org/'
|
|
558
561
|
await connectedPage?.goto(newUrl)
|
|
559
|
-
await connectedPage?.waitForLoadState('
|
|
562
|
+
await connectedPage?.waitForLoadState('domcontentloaded')
|
|
560
563
|
|
|
561
564
|
expect(connectedPage?.url()).toBe(newUrl)
|
|
562
|
-
expect(await connectedPage?.title()).toContain('
|
|
565
|
+
expect(await connectedPage?.title()).toContain('Example Domain')
|
|
563
566
|
|
|
564
567
|
// Verify execution after navigation
|
|
565
568
|
expect(await connectedPage?.evaluate(() => 3 + 3)).toBe(6)
|
|
@@ -571,13 +574,13 @@ describe('MCP Server Tests', () => {
|
|
|
571
574
|
it('should support multiple concurrent tabs', async () => {
|
|
572
575
|
const browserContext = getBrowserContext()
|
|
573
576
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
574
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
577
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
575
578
|
|
|
576
579
|
// Tab A
|
|
577
580
|
const pageA = await browserContext.newPage()
|
|
578
581
|
await pageA.goto('https://example.com/tab-a')
|
|
579
582
|
await pageA.bringToFront()
|
|
580
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
583
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
581
584
|
await serviceWorker.evaluate(async () => {
|
|
582
585
|
await globalThis.toggleExtensionForActiveTab()
|
|
583
586
|
})
|
|
@@ -586,7 +589,7 @@ describe('MCP Server Tests', () => {
|
|
|
586
589
|
const pageB = await browserContext.newPage()
|
|
587
590
|
await pageB.goto('https://example.com/tab-b')
|
|
588
591
|
await pageB.bringToFront()
|
|
589
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
592
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
590
593
|
await serviceWorker.evaluate(async () => {
|
|
591
594
|
await globalThis.toggleExtensionForActiveTab()
|
|
592
595
|
})
|
|
@@ -616,7 +619,7 @@ describe('MCP Server Tests', () => {
|
|
|
616
619
|
expect(targetIds.idA).not.toBe(targetIds.idB)
|
|
617
620
|
|
|
618
621
|
// Verify independent connections
|
|
619
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
622
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
620
623
|
|
|
621
624
|
const pages = browser.contexts()[0].pages()
|
|
622
625
|
|
|
@@ -664,7 +667,7 @@ describe('MCP Server Tests', () => {
|
|
|
664
667
|
await page.bringToFront()
|
|
665
668
|
|
|
666
669
|
// Wait for load
|
|
667
|
-
await page.waitForLoadState('
|
|
670
|
+
await page.waitForLoadState('domcontentloaded')
|
|
668
671
|
|
|
669
672
|
// 2. Enable extension for this page
|
|
670
673
|
await serviceWorker.evaluate(async () => {
|
|
@@ -672,9 +675,9 @@ describe('MCP Server Tests', () => {
|
|
|
672
675
|
})
|
|
673
676
|
|
|
674
677
|
// 3. Verify via CDP that the correct URL is shown
|
|
675
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
678
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
676
679
|
// Wait for sync
|
|
677
|
-
await new Promise(r => setTimeout(r,
|
|
680
|
+
await new Promise(r => setTimeout(r, 100))
|
|
678
681
|
|
|
679
682
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url() === targetUrl)
|
|
680
683
|
|
|
@@ -694,7 +697,7 @@ describe('MCP Server Tests', () => {
|
|
|
694
697
|
const page = pages[0]
|
|
695
698
|
|
|
696
699
|
await page.goto('https://example.com/disconnect-test')
|
|
697
|
-
await page.waitForLoadState('
|
|
700
|
+
await page.waitForLoadState('domcontentloaded')
|
|
698
701
|
await page.bringToFront()
|
|
699
702
|
|
|
700
703
|
// Enable extension on this page
|
|
@@ -705,7 +708,7 @@ describe('MCP Server Tests', () => {
|
|
|
705
708
|
expect(initialEnable.isConnected).toBe(true)
|
|
706
709
|
|
|
707
710
|
// Wait for extension to fully connect
|
|
708
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
711
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
709
712
|
|
|
710
713
|
// Verify MCP can see the page
|
|
711
714
|
const beforeDisconnect = await client.callTool({
|
|
@@ -732,7 +735,7 @@ describe('MCP Server Tests', () => {
|
|
|
732
735
|
})
|
|
733
736
|
|
|
734
737
|
// Wait for disconnect to complete
|
|
735
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
738
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
736
739
|
|
|
737
740
|
// 3. Verify MCP cannot see the page anymore
|
|
738
741
|
const afterDisconnect = await client.callTool({
|
|
@@ -765,7 +768,7 @@ describe('MCP Server Tests', () => {
|
|
|
765
768
|
|
|
766
769
|
// Wait for extension to fully reconnect and relay server to be ready
|
|
767
770
|
console.log('Waiting for reconnection to stabilize...')
|
|
768
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
771
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
769
772
|
|
|
770
773
|
// 5. Reset the MCP client's playwright connection since it was closed by disconnectEverything
|
|
771
774
|
console.log('Resetting MCP playwright connection...')
|
|
@@ -1167,23 +1170,23 @@ describe('MCP Server Tests', () => {
|
|
|
1167
1170
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1168
1171
|
|
|
1169
1172
|
const page = await browserContext.newPage()
|
|
1170
|
-
const targetUrl = 'https://
|
|
1171
|
-
await page.goto(targetUrl
|
|
1173
|
+
const targetUrl = 'https://example.com/sw-test'
|
|
1174
|
+
await page.goto(targetUrl)
|
|
1172
1175
|
await page.bringToFront()
|
|
1173
1176
|
|
|
1174
1177
|
await serviceWorker.evaluate(async () => {
|
|
1175
1178
|
await globalThis.toggleExtensionForActiveTab()
|
|
1176
1179
|
})
|
|
1177
1180
|
|
|
1178
|
-
await new Promise(r => setTimeout(r,
|
|
1181
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1179
1182
|
|
|
1180
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1183
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1181
1184
|
const pages = browser.contexts()[0].pages()
|
|
1182
|
-
const
|
|
1185
|
+
const testPage = pages.find(p => p.url().includes('sw-test'))
|
|
1183
1186
|
|
|
1184
|
-
expect(
|
|
1185
|
-
expect(
|
|
1186
|
-
expect(
|
|
1187
|
+
expect(testPage).toBeDefined()
|
|
1188
|
+
expect(testPage?.url()).toContain('sw-test')
|
|
1189
|
+
expect(testPage?.url()).not.toContain('sw.js')
|
|
1187
1190
|
|
|
1188
1191
|
await browser.close()
|
|
1189
1192
|
await page.close()
|
|
@@ -1203,7 +1206,7 @@ describe('MCP Server Tests', () => {
|
|
|
1203
1206
|
})
|
|
1204
1207
|
|
|
1205
1208
|
for (let i = 0; i < 5; i++) {
|
|
1206
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1209
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1207
1210
|
const pages = browser.contexts()[0].pages()
|
|
1208
1211
|
const testPage = pages.find(p => p.url().includes('repeated-test'))
|
|
1209
1212
|
|
|
@@ -1211,7 +1214,7 @@ describe('MCP Server Tests', () => {
|
|
|
1211
1214
|
expect(testPage?.url()).toBe(targetUrl)
|
|
1212
1215
|
|
|
1213
1216
|
await browser.close()
|
|
1214
|
-
await new Promise(r => setTimeout(r,
|
|
1217
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1215
1218
|
}
|
|
1216
1219
|
|
|
1217
1220
|
await page.close()
|
|
@@ -1230,7 +1233,7 @@ describe('MCP Server Tests', () => {
|
|
|
1230
1233
|
await globalThis.toggleExtensionForActiveTab()
|
|
1231
1234
|
})
|
|
1232
1235
|
|
|
1233
|
-
await new Promise(r => setTimeout(r,
|
|
1236
|
+
await new Promise(r => setTimeout(r, 400))
|
|
1234
1237
|
|
|
1235
1238
|
const [mcpResult, cdpBrowser] = await Promise.all([
|
|
1236
1239
|
client.callTool({
|
|
@@ -1243,7 +1246,7 @@ describe('MCP Server Tests', () => {
|
|
|
1243
1246
|
`,
|
|
1244
1247
|
},
|
|
1245
1248
|
}),
|
|
1246
|
-
chromium.connectOverCDP(getCdpUrl())
|
|
1249
|
+
chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1247
1250
|
])
|
|
1248
1251
|
|
|
1249
1252
|
const mcpOutput = (mcpResult as any).content[0].text
|
|
@@ -1262,30 +1265,46 @@ describe('MCP Server Tests', () => {
|
|
|
1262
1265
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1263
1266
|
|
|
1264
1267
|
const page = await browserContext.newPage()
|
|
1265
|
-
|
|
1266
|
-
|
|
1268
|
+
await page.setContent(`
|
|
1269
|
+
<html>
|
|
1270
|
+
<head><title>Iframe Test Page</title></head>
|
|
1271
|
+
<body>
|
|
1272
|
+
<h1>Iframe Heavy Page</h1>
|
|
1273
|
+
<iframe src="about:blank" id="frame1"></iframe>
|
|
1274
|
+
<iframe src="about:blank" id="frame2"></iframe>
|
|
1275
|
+
<iframe src="about:blank" id="frame3"></iframe>
|
|
1276
|
+
</body>
|
|
1277
|
+
</html>
|
|
1278
|
+
`)
|
|
1267
1279
|
await page.bringToFront()
|
|
1268
1280
|
|
|
1269
1281
|
await serviceWorker.evaluate(async () => {
|
|
1270
1282
|
await globalThis.toggleExtensionForActiveTab()
|
|
1271
1283
|
})
|
|
1272
1284
|
|
|
1273
|
-
await new Promise(r => setTimeout(r,
|
|
1285
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1274
1286
|
|
|
1275
1287
|
for (let i = 0; i < 3; i++) {
|
|
1276
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1288
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1277
1289
|
const pages = browser.contexts()[0].pages()
|
|
1278
|
-
|
|
1290
|
+
let iframePage
|
|
1291
|
+
for (const p of pages) {
|
|
1292
|
+
const html = await p.content()
|
|
1293
|
+
if (html.includes('Iframe Heavy Page')) {
|
|
1294
|
+
iframePage = p
|
|
1295
|
+
break
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1279
1298
|
|
|
1280
|
-
expect(
|
|
1281
|
-
expect(
|
|
1299
|
+
expect(iframePage).toBeDefined()
|
|
1300
|
+
expect(iframePage?.url()).toContain('about:')
|
|
1282
1301
|
|
|
1283
1302
|
await browser.close()
|
|
1284
|
-
await new Promise(r => setTimeout(r,
|
|
1303
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1285
1304
|
}
|
|
1286
1305
|
|
|
1287
1306
|
await page.close()
|
|
1288
|
-
},
|
|
1307
|
+
}, 30000)
|
|
1289
1308
|
|
|
1290
1309
|
it('should capture screenshot correctly', async () => {
|
|
1291
1310
|
const browserContext = getBrowserContext()
|
|
@@ -1299,7 +1318,7 @@ describe('MCP Server Tests', () => {
|
|
|
1299
1318
|
await globalThis.toggleExtensionForActiveTab()
|
|
1300
1319
|
})
|
|
1301
1320
|
|
|
1302
|
-
await new Promise(r => setTimeout(r,
|
|
1321
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1303
1322
|
|
|
1304
1323
|
const capturedCommands: CDPCommand[] = []
|
|
1305
1324
|
const commandHandler = ({ command }: { clientId: string; command: CDPCommand }) => {
|
|
@@ -1309,7 +1328,7 @@ describe('MCP Server Tests', () => {
|
|
|
1309
1328
|
}
|
|
1310
1329
|
testCtx!.relayServer.on('cdp:command', commandHandler)
|
|
1311
1330
|
|
|
1312
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1331
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1313
1332
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
1314
1333
|
|
|
1315
1334
|
expect(cdpPage).toBeDefined()
|
|
@@ -1428,7 +1447,7 @@ describe('MCP Server Tests', () => {
|
|
|
1428
1447
|
await globalThis.toggleExtensionForActiveTab()
|
|
1429
1448
|
})
|
|
1430
1449
|
|
|
1431
|
-
await new Promise(r => setTimeout(r,
|
|
1450
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1432
1451
|
|
|
1433
1452
|
const capturedCommands: CDPCommand[] = []
|
|
1434
1453
|
const commandHandler = ({ command }: { clientId: string; command: CDPCommand }) => {
|
|
@@ -1438,7 +1457,7 @@ describe('MCP Server Tests', () => {
|
|
|
1438
1457
|
}
|
|
1439
1458
|
testCtx!.relayServer.on('cdp:command', commandHandler)
|
|
1440
1459
|
|
|
1441
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1460
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1442
1461
|
let cdpPage
|
|
1443
1462
|
for (const p of browser.contexts()[0].pages()) {
|
|
1444
1463
|
const html = await p.content()
|
|
@@ -1496,7 +1515,7 @@ describe('MCP Server Tests', () => {
|
|
|
1496
1515
|
await globalThis.toggleExtensionForActiveTab()
|
|
1497
1516
|
})
|
|
1498
1517
|
|
|
1499
|
-
await new Promise(r => setTimeout(r,
|
|
1518
|
+
await new Promise(r => setTimeout(r, 400))
|
|
1500
1519
|
|
|
1501
1520
|
const result = await client.callTool({
|
|
1502
1521
|
name: 'execute',
|
|
@@ -1531,6 +1550,193 @@ describe('MCP Server Tests', () => {
|
|
|
1531
1550
|
await page.close()
|
|
1532
1551
|
}, 60000)
|
|
1533
1552
|
|
|
1553
|
+
it('should get styles for element using getStylesForLocator', async () => {
|
|
1554
|
+
const browserContext = getBrowserContext()
|
|
1555
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1556
|
+
|
|
1557
|
+
const page = await browserContext.newPage()
|
|
1558
|
+
await page.setContent(`
|
|
1559
|
+
<html>
|
|
1560
|
+
<head>
|
|
1561
|
+
<style>
|
|
1562
|
+
body { font-family: Arial, sans-serif; color: #333; }
|
|
1563
|
+
.container { padding: 20px; margin: 10px; }
|
|
1564
|
+
#main-btn { background-color: blue; color: white; border-radius: 4px; }
|
|
1565
|
+
.btn { padding: 8px 16px; }
|
|
1566
|
+
</style>
|
|
1567
|
+
</head>
|
|
1568
|
+
<body>
|
|
1569
|
+
<div class="container">
|
|
1570
|
+
<button id="main-btn" class="btn" style="font-weight: bold;">Click Me</button>
|
|
1571
|
+
</div>
|
|
1572
|
+
</body>
|
|
1573
|
+
</html>
|
|
1574
|
+
`)
|
|
1575
|
+
await page.bringToFront()
|
|
1576
|
+
|
|
1577
|
+
await serviceWorker.evaluate(async () => {
|
|
1578
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
1579
|
+
})
|
|
1580
|
+
|
|
1581
|
+
await new Promise(r => setTimeout(r, 400))
|
|
1582
|
+
|
|
1583
|
+
const stylesResult = await client.callTool({
|
|
1584
|
+
name: 'execute',
|
|
1585
|
+
arguments: {
|
|
1586
|
+
code: js`
|
|
1587
|
+
let testPage;
|
|
1588
|
+
for (const p of context.pages()) {
|
|
1589
|
+
const html = await p.content();
|
|
1590
|
+
if (html.includes('main-btn')) { testPage = p; break; }
|
|
1591
|
+
}
|
|
1592
|
+
if (!testPage) throw new Error('Test page not found');
|
|
1593
|
+
const btn = testPage.locator('#main-btn');
|
|
1594
|
+
const styles = await getStylesForLocator({ locator: btn });
|
|
1595
|
+
return styles;
|
|
1596
|
+
`,
|
|
1597
|
+
timeout: 30000,
|
|
1598
|
+
},
|
|
1599
|
+
})
|
|
1600
|
+
|
|
1601
|
+
expect(stylesResult.isError).toBeFalsy()
|
|
1602
|
+
const stylesText = (stylesResult.content as any)[0]?.text || ''
|
|
1603
|
+
expect(stylesText).toMatchInlineSnapshot(`
|
|
1604
|
+
"Return value:
|
|
1605
|
+
{
|
|
1606
|
+
"element": "button#main-btn.btn",
|
|
1607
|
+
"inlineStyle": {
|
|
1608
|
+
"font-weight": "bold"
|
|
1609
|
+
},
|
|
1610
|
+
"rules": [
|
|
1611
|
+
{
|
|
1612
|
+
"selector": ".btn",
|
|
1613
|
+
"source": null,
|
|
1614
|
+
"origin": "regular",
|
|
1615
|
+
"declarations": {
|
|
1616
|
+
"padding": "8px 16px",
|
|
1617
|
+
"padding-top": "8px",
|
|
1618
|
+
"padding-right": "16px",
|
|
1619
|
+
"padding-bottom": "8px",
|
|
1620
|
+
"padding-left": "16px"
|
|
1621
|
+
},
|
|
1622
|
+
"inheritedFrom": null
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
"selector": "#main-btn",
|
|
1626
|
+
"source": null,
|
|
1627
|
+
"origin": "regular",
|
|
1628
|
+
"declarations": {
|
|
1629
|
+
"background-color": "blue",
|
|
1630
|
+
"color": "white",
|
|
1631
|
+
"border-radius": "4px",
|
|
1632
|
+
"border-top-left-radius": "4px",
|
|
1633
|
+
"border-top-right-radius": "4px",
|
|
1634
|
+
"border-bottom-right-radius": "4px",
|
|
1635
|
+
"border-bottom-left-radius": "4px"
|
|
1636
|
+
},
|
|
1637
|
+
"inheritedFrom": null
|
|
1638
|
+
},
|
|
1639
|
+
{
|
|
1640
|
+
"selector": ".container",
|
|
1641
|
+
"source": null,
|
|
1642
|
+
"origin": "regular",
|
|
1643
|
+
"declarations": {
|
|
1644
|
+
"padding": "20px",
|
|
1645
|
+
"margin": "10px",
|
|
1646
|
+
"padding-top": "20px",
|
|
1647
|
+
"padding-right": "20px",
|
|
1648
|
+
"padding-bottom": "20px",
|
|
1649
|
+
"padding-left": "20px",
|
|
1650
|
+
"margin-top": "10px",
|
|
1651
|
+
"margin-right": "10px",
|
|
1652
|
+
"margin-bottom": "10px",
|
|
1653
|
+
"margin-left": "10px"
|
|
1654
|
+
},
|
|
1655
|
+
"inheritedFrom": "ancestor[1]"
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
"selector": "body",
|
|
1659
|
+
"source": null,
|
|
1660
|
+
"origin": "regular",
|
|
1661
|
+
"declarations": {
|
|
1662
|
+
"font-family": "Arial, sans-serif",
|
|
1663
|
+
"color": "rgb(51, 51, 51)"
|
|
1664
|
+
},
|
|
1665
|
+
"inheritedFrom": "ancestor[2]"
|
|
1666
|
+
}
|
|
1667
|
+
]
|
|
1668
|
+
}"
|
|
1669
|
+
`)
|
|
1670
|
+
|
|
1671
|
+
const formattedResult = await client.callTool({
|
|
1672
|
+
name: 'execute',
|
|
1673
|
+
arguments: {
|
|
1674
|
+
code: js`
|
|
1675
|
+
let testPage;
|
|
1676
|
+
for (const p of context.pages()) {
|
|
1677
|
+
const html = await p.content();
|
|
1678
|
+
if (html.includes('main-btn')) { testPage = p; break; }
|
|
1679
|
+
}
|
|
1680
|
+
if (!testPage) throw new Error('Test page not found');
|
|
1681
|
+
const btn = testPage.locator('#main-btn');
|
|
1682
|
+
const styles = await getStylesForLocator({ locator: btn });
|
|
1683
|
+
return formatStylesAsText(styles);
|
|
1684
|
+
`,
|
|
1685
|
+
timeout: 30000,
|
|
1686
|
+
},
|
|
1687
|
+
})
|
|
1688
|
+
|
|
1689
|
+
expect(formattedResult.isError).toBeFalsy()
|
|
1690
|
+
const formattedText = (formattedResult.content as any)[0]?.text || ''
|
|
1691
|
+
expect(formattedText).toMatchInlineSnapshot(`
|
|
1692
|
+
"Return value:
|
|
1693
|
+
Element: button#main-btn.btn
|
|
1694
|
+
|
|
1695
|
+
Inline styles:
|
|
1696
|
+
font-weight: bold
|
|
1697
|
+
|
|
1698
|
+
Matched rules:
|
|
1699
|
+
.btn {
|
|
1700
|
+
padding: 8px 16px;
|
|
1701
|
+
padding-top: 8px;
|
|
1702
|
+
padding-right: 16px;
|
|
1703
|
+
padding-bottom: 8px;
|
|
1704
|
+
padding-left: 16px;
|
|
1705
|
+
}
|
|
1706
|
+
#main-btn {
|
|
1707
|
+
background-color: blue;
|
|
1708
|
+
color: white;
|
|
1709
|
+
border-radius: 4px;
|
|
1710
|
+
border-top-left-radius: 4px;
|
|
1711
|
+
border-top-right-radius: 4px;
|
|
1712
|
+
border-bottom-right-radius: 4px;
|
|
1713
|
+
border-bottom-left-radius: 4px;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
Inherited from ancestor[1]:
|
|
1717
|
+
.container {
|
|
1718
|
+
padding: 20px;
|
|
1719
|
+
margin: 10px;
|
|
1720
|
+
padding-top: 20px;
|
|
1721
|
+
padding-right: 20px;
|
|
1722
|
+
padding-bottom: 20px;
|
|
1723
|
+
padding-left: 20px;
|
|
1724
|
+
margin-top: 10px;
|
|
1725
|
+
margin-right: 10px;
|
|
1726
|
+
margin-bottom: 10px;
|
|
1727
|
+
margin-left: 10px;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
Inherited from ancestor[2]:
|
|
1731
|
+
body {
|
|
1732
|
+
font-family: Arial, sans-serif;
|
|
1733
|
+
color: rgb(51, 51, 51);
|
|
1734
|
+
}"
|
|
1735
|
+
`)
|
|
1736
|
+
|
|
1737
|
+
await page.close()
|
|
1738
|
+
}, 60000)
|
|
1739
|
+
|
|
1534
1740
|
it('should return correct layout metrics via CDP', async () => {
|
|
1535
1741
|
const browserContext = getBrowserContext()
|
|
1536
1742
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
@@ -1543,13 +1749,13 @@ describe('MCP Server Tests', () => {
|
|
|
1543
1749
|
await globalThis.toggleExtensionForActiveTab()
|
|
1544
1750
|
})
|
|
1545
1751
|
|
|
1546
|
-
await new Promise(r => setTimeout(r,
|
|
1752
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1547
1753
|
|
|
1548
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1754
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1549
1755
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
1550
1756
|
expect(cdpPage).toBeDefined()
|
|
1551
1757
|
|
|
1552
|
-
const wsUrl = getCdpUrl()
|
|
1758
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
1553
1759
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
1554
1760
|
|
|
1555
1761
|
const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
|
|
@@ -1606,7 +1812,7 @@ describe('MCP Server Tests', () => {
|
|
|
1606
1812
|
console.log('window.devicePixelRatio:', windowDpr)
|
|
1607
1813
|
expect(windowDpr).toBe(1)
|
|
1608
1814
|
|
|
1609
|
-
cdpSession.
|
|
1815
|
+
cdpSession.close()
|
|
1610
1816
|
await browser.close()
|
|
1611
1817
|
await page.close()
|
|
1612
1818
|
}, 60000)
|
|
@@ -1623,20 +1829,20 @@ describe('MCP Server Tests', () => {
|
|
|
1623
1829
|
await globalThis.toggleExtensionForActiveTab()
|
|
1624
1830
|
})
|
|
1625
1831
|
|
|
1626
|
-
await new Promise(r => setTimeout(r,
|
|
1832
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1627
1833
|
|
|
1628
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
1834
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1629
1835
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
1630
1836
|
expect(cdpPage).toBeDefined()
|
|
1631
1837
|
|
|
1632
|
-
const wsUrl = getCdpUrl()
|
|
1838
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
1633
1839
|
const client = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
1634
1840
|
|
|
1635
1841
|
const layoutMetrics = await client.send('Page.getLayoutMetrics')
|
|
1636
1842
|
expect(layoutMetrics.cssVisualViewport).toBeDefined()
|
|
1637
1843
|
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
|
|
1638
1844
|
|
|
1639
|
-
client.
|
|
1845
|
+
client.close()
|
|
1640
1846
|
await browser.close()
|
|
1641
1847
|
await page.close()
|
|
1642
1848
|
}, 60000)
|
|
@@ -1648,20 +1854,20 @@ describe('MCP Server Tests', () => {
|
|
|
1648
1854
|
await serviceWorker.evaluate(async () => {
|
|
1649
1855
|
await globalThis.disconnectEverything()
|
|
1650
1856
|
})
|
|
1651
|
-
await new Promise(r => setTimeout(r,
|
|
1857
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1652
1858
|
|
|
1653
1859
|
const targetUrl = 'https://example.com/'
|
|
1654
1860
|
|
|
1655
1861
|
const enableResult = await serviceWorker.evaluate(async (url) => {
|
|
1656
1862
|
const tab = await chrome.tabs.create({ url, active: true })
|
|
1657
|
-
await new Promise(r => setTimeout(r,
|
|
1863
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1658
1864
|
return await globalThis.toggleExtensionForActiveTab()
|
|
1659
1865
|
}, targetUrl)
|
|
1660
1866
|
|
|
1661
1867
|
console.log('Extension enabled:', enableResult)
|
|
1662
1868
|
expect(enableResult.isConnected).toBe(true)
|
|
1663
1869
|
|
|
1664
|
-
await new Promise(r => setTimeout(r,
|
|
1870
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1665
1871
|
|
|
1666
1872
|
const { Stagehand } = await import('@browserbasehq/stagehand')
|
|
1667
1873
|
|
|
@@ -1670,7 +1876,7 @@ describe('MCP Server Tests', () => {
|
|
|
1670
1876
|
verbose: 1,
|
|
1671
1877
|
disablePino: true,
|
|
1672
1878
|
localBrowserLaunchOptions: {
|
|
1673
|
-
cdpUrl: getCdpUrl(),
|
|
1879
|
+
cdpUrl: getCdpUrl({ port: TEST_PORT }),
|
|
1674
1880
|
},
|
|
1675
1881
|
})
|
|
1676
1882
|
|
|
@@ -1679,7 +1885,7 @@ describe('MCP Server Tests', () => {
|
|
|
1679
1885
|
console.log('Stagehand initialized')
|
|
1680
1886
|
|
|
1681
1887
|
const context = stagehand.context
|
|
1682
|
-
console.log('Stagehand context:', context)
|
|
1888
|
+
// console.log('Stagehand context:', context)
|
|
1683
1889
|
expect(context).toBeDefined()
|
|
1684
1890
|
|
|
1685
1891
|
const pages = context.pages()
|
|
@@ -1711,7 +1917,7 @@ describe('MCP Server Tests', () => {
|
|
|
1711
1917
|
await serviceWorker.evaluate(async () => {
|
|
1712
1918
|
await globalThis.toggleExtensionForActiveTab()
|
|
1713
1919
|
})
|
|
1714
|
-
await new Promise(r => setTimeout(r,
|
|
1920
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1715
1921
|
|
|
1716
1922
|
const result = await client.callTool({
|
|
1717
1923
|
name: 'execute',
|
|
@@ -1764,6 +1970,12 @@ describe('CDP Session Tests', () => {
|
|
|
1764
1970
|
|
|
1765
1971
|
beforeAll(async () => {
|
|
1766
1972
|
testCtx = await setupTestContext({ tempDirPrefix: 'pw-cdp-test-' })
|
|
1973
|
+
|
|
1974
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
|
|
1975
|
+
await serviceWorker.evaluate(async () => {
|
|
1976
|
+
await globalThis.disconnectEverything()
|
|
1977
|
+
})
|
|
1978
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1767
1979
|
}, 600000)
|
|
1768
1980
|
|
|
1769
1981
|
afterAll(async () => {
|
|
@@ -1776,7 +1988,7 @@ describe('CDP Session Tests', () => {
|
|
|
1776
1988
|
return testCtx.browserContext
|
|
1777
1989
|
}
|
|
1778
1990
|
|
|
1779
|
-
it('should
|
|
1991
|
+
it('should use Debugger class to set breakpoints and inspect variables', async () => {
|
|
1780
1992
|
const browserContext = getBrowserContext()
|
|
1781
1993
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1782
1994
|
|
|
@@ -1787,19 +1999,23 @@ describe('CDP Session Tests', () => {
|
|
|
1787
1999
|
await serviceWorker.evaluate(async () => {
|
|
1788
2000
|
await globalThis.toggleExtensionForActiveTab()
|
|
1789
2001
|
})
|
|
1790
|
-
await new Promise(r => setTimeout(r,
|
|
2002
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1791
2003
|
|
|
1792
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
2004
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1793
2005
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
1794
2006
|
expect(cdpPage).toBeDefined()
|
|
1795
2007
|
|
|
1796
|
-
const wsUrl = getCdpUrl()
|
|
2008
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
1797
2009
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
1798
|
-
|
|
2010
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2011
|
+
|
|
2012
|
+
await dbg.enable()
|
|
1799
2013
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2014
|
+
expect(dbg.isPaused()).toBe(false)
|
|
2015
|
+
|
|
2016
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
2017
|
+
cdpSession.on('Debugger.paused', () => {
|
|
2018
|
+
resolve()
|
|
1803
2019
|
})
|
|
1804
2020
|
})
|
|
1805
2021
|
|
|
@@ -1807,107 +2023,215 @@ describe('CDP Session Tests', () => {
|
|
|
1807
2023
|
(function testFunction() {
|
|
1808
2024
|
const localVar = 'hello';
|
|
1809
2025
|
const numberVar = 42;
|
|
1810
|
-
const objVar = { key: 'value', nested: { a: 1 } };
|
|
1811
2026
|
debugger;
|
|
1812
2027
|
return localVar + numberVar;
|
|
1813
2028
|
})()
|
|
1814
2029
|
`)
|
|
1815
2030
|
|
|
1816
|
-
|
|
2031
|
+
await Promise.race([
|
|
1817
2032
|
pausedPromise,
|
|
1818
2033
|
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
|
|
1819
2034
|
])
|
|
1820
2035
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2036
|
+
expect(dbg.isPaused()).toBe(true)
|
|
2037
|
+
|
|
2038
|
+
const location = await dbg.getLocation()
|
|
2039
|
+
expect(location.callstack[0].functionName).toBe('testFunction')
|
|
2040
|
+
expect(location.sourceContext).toContain('debugger')
|
|
1826
2041
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
stackTrace: stackTrace.slice(0, 3),
|
|
1830
|
-
}).toMatchInlineSnapshot(`
|
|
2042
|
+
const vars = await dbg.inspectLocalVariables()
|
|
2043
|
+
expect(vars).toMatchInlineSnapshot(`
|
|
1831
2044
|
{
|
|
1832
|
-
"
|
|
1833
|
-
"
|
|
1834
|
-
{
|
|
1835
|
-
"columnNumber": 16,
|
|
1836
|
-
"functionName": "testFunction",
|
|
1837
|
-
"lineNumber": 4,
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
"columnNumber": 14,
|
|
1841
|
-
"functionName": "(anonymous)",
|
|
1842
|
-
"lineNumber": 6,
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
"columnNumber": 29,
|
|
1846
|
-
"functionName": "evaluate",
|
|
1847
|
-
"lineNumber": 289,
|
|
1848
|
-
},
|
|
1849
|
-
],
|
|
2045
|
+
"localVar": "hello",
|
|
2046
|
+
"numberVar": 42,
|
|
1850
2047
|
}
|
|
1851
2048
|
`)
|
|
1852
2049
|
|
|
1853
|
-
const
|
|
1854
|
-
|
|
2050
|
+
const evalResult = await dbg.evaluate({ expression: 'localVar + " world"' })
|
|
2051
|
+
expect(evalResult.value).toBe('hello world')
|
|
1855
2052
|
|
|
1856
|
-
|
|
1857
|
-
|
|
2053
|
+
await dbg.resume()
|
|
2054
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2055
|
+
expect(dbg.isPaused()).toBe(false)
|
|
1858
2056
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
})
|
|
2057
|
+
cdpSession.close()
|
|
2058
|
+
await browser.close()
|
|
2059
|
+
await page.close()
|
|
2060
|
+
}, 60000)
|
|
1864
2061
|
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2062
|
+
it('should list scripts with Debugger class', async () => {
|
|
2063
|
+
const browserContext = getBrowserContext()
|
|
2064
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2065
|
+
|
|
2066
|
+
const page = await browserContext.newPage()
|
|
2067
|
+
await page.setContent(`
|
|
2068
|
+
<html>
|
|
2069
|
+
<head>
|
|
2070
|
+
<script src="data:text/javascript,function testScript() { return 42; }"></script>
|
|
2071
|
+
</head>
|
|
2072
|
+
<body><h1>Script Test</h1></body>
|
|
2073
|
+
</html>
|
|
2074
|
+
`)
|
|
2075
|
+
await page.bringToFront()
|
|
2076
|
+
|
|
2077
|
+
await serviceWorker.evaluate(async () => {
|
|
2078
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2079
|
+
})
|
|
2080
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2081
|
+
|
|
2082
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2083
|
+
let cdpPage
|
|
2084
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
2085
|
+
const html = await p.content()
|
|
2086
|
+
if (html.includes('Script Test')) {
|
|
2087
|
+
cdpPage = p
|
|
2088
|
+
break
|
|
1871
2089
|
}
|
|
1872
2090
|
}
|
|
2091
|
+
expect(cdpPage).toBeDefined()
|
|
1873
2092
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2093
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2094
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2095
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2096
|
+
|
|
2097
|
+
const scripts = await dbg.listScripts()
|
|
2098
|
+
expect(scripts.length).toBeGreaterThan(0)
|
|
2099
|
+
expect(scripts[0]).toHaveProperty('scriptId')
|
|
2100
|
+
expect(scripts[0]).toHaveProperty('url')
|
|
2101
|
+
|
|
2102
|
+
const dataScripts = await dbg.listScripts({ search: 'data:' })
|
|
2103
|
+
expect(dataScripts.length).toBeGreaterThan(0)
|
|
2104
|
+
|
|
2105
|
+
cdpSession.close()
|
|
2106
|
+
await browser.close()
|
|
2107
|
+
await page.close()
|
|
2108
|
+
}, 60000)
|
|
2109
|
+
|
|
2110
|
+
it('should manage breakpoints with Debugger class', async () => {
|
|
2111
|
+
const browserContext = getBrowserContext()
|
|
2112
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2113
|
+
|
|
2114
|
+
const page = await browserContext.newPage()
|
|
2115
|
+
await page.setContent(`
|
|
2116
|
+
<html>
|
|
2117
|
+
<head>
|
|
2118
|
+
<script src="data:text/javascript,function testFunc() { return 42; }"></script>
|
|
2119
|
+
</head>
|
|
2120
|
+
<body></body>
|
|
2121
|
+
</html>
|
|
1889
2122
|
`)
|
|
2123
|
+
await page.bringToFront()
|
|
1890
2124
|
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
expression: 'localVar + " world " + numberVar',
|
|
2125
|
+
await serviceWorker.evaluate(async () => {
|
|
2126
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
1894
2127
|
})
|
|
2128
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1895
2129
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2130
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2131
|
+
let cdpPage
|
|
2132
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
2133
|
+
const html = await p.content()
|
|
2134
|
+
if (html.includes('testFunc')) {
|
|
2135
|
+
cdpPage = p
|
|
2136
|
+
break
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
expect(cdpPage).toBeDefined()
|
|
2140
|
+
|
|
2141
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2142
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2143
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2144
|
+
|
|
2145
|
+
await dbg.enable()
|
|
2146
|
+
|
|
2147
|
+
expect(dbg.listBreakpoints()).toHaveLength(0)
|
|
2148
|
+
|
|
2149
|
+
const bpId = await dbg.setBreakpoint({ file: 'https://example.com/test.js', line: 1 })
|
|
2150
|
+
expect(typeof bpId).toBe('string')
|
|
2151
|
+
expect(dbg.listBreakpoints()).toHaveLength(1)
|
|
2152
|
+
expect(dbg.listBreakpoints()[0]).toMatchObject({
|
|
2153
|
+
id: bpId,
|
|
2154
|
+
file: 'https://example.com/test.js',
|
|
2155
|
+
line: 1,
|
|
2156
|
+
})
|
|
2157
|
+
|
|
2158
|
+
await dbg.deleteBreakpoint({ breakpointId: bpId })
|
|
2159
|
+
expect(dbg.listBreakpoints()).toHaveLength(0)
|
|
2160
|
+
|
|
2161
|
+
cdpSession.close()
|
|
2162
|
+
await browser.close()
|
|
2163
|
+
await page.close()
|
|
2164
|
+
}, 60000)
|
|
2165
|
+
|
|
2166
|
+
it('should step through code with Debugger class', async () => {
|
|
2167
|
+
const browserContext = getBrowserContext()
|
|
2168
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2169
|
+
|
|
2170
|
+
const page = await browserContext.newPage()
|
|
2171
|
+
await page.goto('https://example.com/')
|
|
2172
|
+
await page.bringToFront()
|
|
2173
|
+
|
|
2174
|
+
await serviceWorker.evaluate(async () => {
|
|
2175
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2176
|
+
})
|
|
2177
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2178
|
+
|
|
2179
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2180
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2181
|
+
expect(cdpPage).toBeDefined()
|
|
2182
|
+
|
|
2183
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2184
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2185
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2186
|
+
|
|
2187
|
+
await dbg.enable()
|
|
2188
|
+
|
|
2189
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
2190
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
2191
|
+
})
|
|
2192
|
+
|
|
2193
|
+
cdpPage!.evaluate(`
|
|
2194
|
+
(function outer() {
|
|
2195
|
+
function inner() {
|
|
2196
|
+
const x = 1;
|
|
2197
|
+
debugger;
|
|
2198
|
+
const y = 2;
|
|
2199
|
+
return x + y;
|
|
2200
|
+
}
|
|
2201
|
+
const result = inner();
|
|
2202
|
+
return result;
|
|
2203
|
+
})()
|
|
1906
2204
|
`)
|
|
1907
2205
|
|
|
1908
|
-
await
|
|
1909
|
-
|
|
1910
|
-
|
|
2206
|
+
await pausedPromise
|
|
2207
|
+
expect(dbg.isPaused()).toBe(true)
|
|
2208
|
+
|
|
2209
|
+
const location1 = await dbg.getLocation()
|
|
2210
|
+
expect(location1.callstack.length).toBeGreaterThanOrEqual(2)
|
|
2211
|
+
expect(location1.callstack[0].functionName).toBe('inner')
|
|
2212
|
+
expect(location1.callstack[1].functionName).toBe('outer')
|
|
2213
|
+
|
|
2214
|
+
const stepOverPromise = new Promise<void>((resolve) => {
|
|
2215
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
2216
|
+
})
|
|
2217
|
+
await dbg.stepOver()
|
|
2218
|
+
await stepOverPromise
|
|
2219
|
+
|
|
2220
|
+
const location2 = await dbg.getLocation()
|
|
2221
|
+
expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber)
|
|
2222
|
+
|
|
2223
|
+
const stepOutPromise = new Promise<void>((resolve) => {
|
|
2224
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
2225
|
+
})
|
|
2226
|
+
await dbg.stepOut()
|
|
2227
|
+
await stepOutPromise
|
|
2228
|
+
|
|
2229
|
+
const location3 = await dbg.getLocation()
|
|
2230
|
+
expect(location3.callstack[0].functionName).toBe('outer')
|
|
2231
|
+
|
|
2232
|
+
await dbg.resume()
|
|
2233
|
+
|
|
2234
|
+
cdpSession.close()
|
|
1911
2235
|
await browser.close()
|
|
1912
2236
|
await page.close()
|
|
1913
2237
|
}, 60000)
|
|
@@ -1923,13 +2247,13 @@ describe('CDP Session Tests', () => {
|
|
|
1923
2247
|
await serviceWorker.evaluate(async () => {
|
|
1924
2248
|
await globalThis.toggleExtensionForActiveTab()
|
|
1925
2249
|
})
|
|
1926
|
-
await new Promise(r => setTimeout(r,
|
|
2250
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1927
2251
|
|
|
1928
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
2252
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1929
2253
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
1930
2254
|
expect(cdpPage).toBeDefined()
|
|
1931
2255
|
|
|
1932
|
-
const wsUrl = getCdpUrl()
|
|
2256
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
1933
2257
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
1934
2258
|
await cdpSession.send('Profiler.enable')
|
|
1935
2259
|
await cdpSession.send('Profiler.start')
|
|
@@ -1957,33 +2281,17 @@ describe('CDP Session Tests', () => {
|
|
|
1957
2281
|
.filter(name => name && name.length > 0)
|
|
1958
2282
|
.slice(0, 10)
|
|
1959
2283
|
|
|
1960
|
-
expect(
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
durationMicroseconds: profile.endTime - profile.startTime,
|
|
1964
|
-
sampleFunctionNames: functionNames,
|
|
1965
|
-
}).toMatchInlineSnapshot(`
|
|
1966
|
-
{
|
|
1967
|
-
"durationMicroseconds": 6962,
|
|
1968
|
-
"hasNodes": true,
|
|
1969
|
-
"nodeCount": 8,
|
|
1970
|
-
"sampleFunctionNames": [
|
|
1971
|
-
"(root)",
|
|
1972
|
-
"(program)",
|
|
1973
|
-
"(idle)",
|
|
1974
|
-
"evaluate",
|
|
1975
|
-
"querySelectorAll",
|
|
1976
|
-
],
|
|
1977
|
-
}
|
|
1978
|
-
`)
|
|
2284
|
+
expect(profile.nodes.length).toBeGreaterThan(0)
|
|
2285
|
+
expect(profile.endTime - profile.startTime).toBeGreaterThan(0)
|
|
2286
|
+
expect(functionNames.every((name) => typeof name === 'string')).toBe(true)
|
|
1979
2287
|
|
|
1980
2288
|
await cdpSession.send('Profiler.disable')
|
|
1981
|
-
cdpSession.
|
|
2289
|
+
cdpSession.close()
|
|
1982
2290
|
await browser.close()
|
|
1983
2291
|
await page.close()
|
|
1984
2292
|
}, 60000)
|
|
1985
2293
|
|
|
1986
|
-
it('should
|
|
2294
|
+
it('should update Target.getTargets URL after page navigation', async () => {
|
|
1987
2295
|
const browserContext = getBrowserContext()
|
|
1988
2296
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
1989
2297
|
|
|
@@ -1994,26 +2302,353 @@ describe('CDP Session Tests', () => {
|
|
|
1994
2302
|
await serviceWorker.evaluate(async () => {
|
|
1995
2303
|
await globalThis.toggleExtensionForActiveTab()
|
|
1996
2304
|
})
|
|
1997
|
-
await new Promise(r => setTimeout(r,
|
|
2305
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1998
2306
|
|
|
1999
|
-
const browser = await chromium.connectOverCDP(getCdpUrl())
|
|
2307
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2000
2308
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2001
2309
|
expect(cdpPage).toBeDefined()
|
|
2002
2310
|
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
console.log('H1 bounding box:', h1Bounds)
|
|
2311
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2312
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2006
2313
|
|
|
2007
|
-
await
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
(window as any).clickedAt = { x: e.clientX, y: e.clientY };
|
|
2011
|
-
});
|
|
2012
|
-
})
|
|
2314
|
+
const initialTargets = await cdpSession.send('Target.getTargets')
|
|
2315
|
+
const initialPageTarget = initialTargets.targetInfos.find(t => t.type === 'page' && t.url.includes('example.com'))
|
|
2316
|
+
expect(initialPageTarget?.url).toBe('https://example.com/')
|
|
2013
2317
|
|
|
2014
|
-
await cdpPage!.
|
|
2318
|
+
await cdpPage!.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
|
|
2319
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2015
2320
|
|
|
2016
|
-
const
|
|
2321
|
+
const afterNavTargets = await cdpSession.send('Target.getTargets')
|
|
2322
|
+
const allPageTargets = afterNavTargets.targetInfos.filter(t => t.type === 'page')
|
|
2323
|
+
|
|
2324
|
+
const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank')
|
|
2325
|
+
expect(aboutBlankTargets).toHaveLength(0)
|
|
2326
|
+
|
|
2327
|
+
const exampleComTargets = allPageTargets.filter(t => t.url.includes('example.com'))
|
|
2328
|
+
expect(exampleComTargets).toHaveLength(0)
|
|
2329
|
+
|
|
2330
|
+
const exampleOrgTargets = allPageTargets.filter(t => t.url.includes('example.org'))
|
|
2331
|
+
expect(exampleOrgTargets).toHaveLength(1)
|
|
2332
|
+
|
|
2333
|
+
cdpSession.close()
|
|
2334
|
+
await browser.close()
|
|
2335
|
+
await page.close()
|
|
2336
|
+
}, 60000)
|
|
2337
|
+
|
|
2338
|
+
it('should return correct targets for multiple pages via Target.getTargets', async () => {
|
|
2339
|
+
const browserContext = getBrowserContext()
|
|
2340
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2341
|
+
|
|
2342
|
+
const page1 = await browserContext.newPage()
|
|
2343
|
+
await page1.goto('https://example.com/')
|
|
2344
|
+
await page1.bringToFront()
|
|
2345
|
+
await serviceWorker.evaluate(async () => {
|
|
2346
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2347
|
+
})
|
|
2348
|
+
|
|
2349
|
+
const page2 = await browserContext.newPage()
|
|
2350
|
+
await page2.goto('https://example.org/')
|
|
2351
|
+
await page2.bringToFront()
|
|
2352
|
+
await serviceWorker.evaluate(async () => {
|
|
2353
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2354
|
+
})
|
|
2355
|
+
|
|
2356
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2357
|
+
|
|
2358
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2359
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2360
|
+
expect(cdpPage).toBeDefined()
|
|
2361
|
+
|
|
2362
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2363
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2364
|
+
|
|
2365
|
+
const { targetInfos } = await cdpSession.send('Target.getTargets')
|
|
2366
|
+
const allPageTargets = targetInfos.filter(t => t.type === 'page')
|
|
2367
|
+
|
|
2368
|
+
const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank')
|
|
2369
|
+
expect(aboutBlankTargets).toHaveLength(0)
|
|
2370
|
+
|
|
2371
|
+
const pageTargets = allPageTargets
|
|
2372
|
+
.map(t => ({ type: t.type, url: t.url }))
|
|
2373
|
+
.sort((a, b) => a.url.localeCompare(b.url))
|
|
2374
|
+
|
|
2375
|
+
expect(pageTargets).toMatchInlineSnapshot(`
|
|
2376
|
+
[
|
|
2377
|
+
{
|
|
2378
|
+
"type": "page",
|
|
2379
|
+
"url": "https://example.com/",
|
|
2380
|
+
},
|
|
2381
|
+
{
|
|
2382
|
+
"type": "page",
|
|
2383
|
+
"url": "https://example.org/",
|
|
2384
|
+
},
|
|
2385
|
+
]
|
|
2386
|
+
`)
|
|
2387
|
+
|
|
2388
|
+
cdpSession.close()
|
|
2389
|
+
await browser.close()
|
|
2390
|
+
await page1.close()
|
|
2391
|
+
await page2.close()
|
|
2392
|
+
}, 60000)
|
|
2393
|
+
|
|
2394
|
+
it('should create CDP session for page after navigation', async () => {
|
|
2395
|
+
const browserContext = getBrowserContext()
|
|
2396
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2397
|
+
|
|
2398
|
+
const page = await browserContext.newPage()
|
|
2399
|
+
await page.goto('https://example.com/')
|
|
2400
|
+
await page.bringToFront()
|
|
2401
|
+
|
|
2402
|
+
await serviceWorker.evaluate(async () => {
|
|
2403
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2404
|
+
})
|
|
2405
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2406
|
+
|
|
2407
|
+
await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' })
|
|
2408
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2409
|
+
|
|
2410
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2411
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.org'))
|
|
2412
|
+
expect(cdpPage).toBeDefined()
|
|
2413
|
+
|
|
2414
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2415
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2416
|
+
|
|
2417
|
+
const evalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2418
|
+
expression: 'document.title',
|
|
2419
|
+
returnByValue: true,
|
|
2420
|
+
})
|
|
2421
|
+
expect(evalResult.result.value).toContain('Example Domain')
|
|
2422
|
+
|
|
2423
|
+
cdpSession.close()
|
|
2424
|
+
await browser.close()
|
|
2425
|
+
await page.close()
|
|
2426
|
+
}, 60000)
|
|
2427
|
+
|
|
2428
|
+
it('should maintain CDP session functionality after page URL change', async () => {
|
|
2429
|
+
const browserContext = getBrowserContext()
|
|
2430
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2431
|
+
|
|
2432
|
+
const page = await browserContext.newPage()
|
|
2433
|
+
const initialUrl = 'https://example.com/'
|
|
2434
|
+
await page.goto(initialUrl)
|
|
2435
|
+
await page.bringToFront()
|
|
2436
|
+
|
|
2437
|
+
await serviceWorker.evaluate(async () => {
|
|
2438
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2439
|
+
})
|
|
2440
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2441
|
+
|
|
2442
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2443
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2444
|
+
expect(cdpPage).toBeDefined()
|
|
2445
|
+
|
|
2446
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2447
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2448
|
+
|
|
2449
|
+
const initialEvalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2450
|
+
expression: 'document.title',
|
|
2451
|
+
returnByValue: true,
|
|
2452
|
+
})
|
|
2453
|
+
expect(initialEvalResult.result.value).toBe('Example Domain')
|
|
2454
|
+
|
|
2455
|
+
const newUrl = 'https://example.org/'
|
|
2456
|
+
await cdpPage!.goto(newUrl, { waitUntil: 'domcontentloaded' })
|
|
2457
|
+
|
|
2458
|
+
expect(cdpPage!.url()).toBe(newUrl)
|
|
2459
|
+
|
|
2460
|
+
const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics')
|
|
2461
|
+
expect(layoutMetrics.cssVisualViewport).toBeDefined()
|
|
2462
|
+
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0)
|
|
2463
|
+
|
|
2464
|
+
const afterNavEvalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2465
|
+
expression: 'document.title',
|
|
2466
|
+
returnByValue: true,
|
|
2467
|
+
})
|
|
2468
|
+
expect(afterNavEvalResult.result.value).toContain('Example Domain')
|
|
2469
|
+
|
|
2470
|
+
const locationResult = await cdpSession.send('Runtime.evaluate', {
|
|
2471
|
+
expression: 'window.location.href',
|
|
2472
|
+
returnByValue: true,
|
|
2473
|
+
})
|
|
2474
|
+
expect(locationResult.result.value).toBe(newUrl)
|
|
2475
|
+
|
|
2476
|
+
cdpSession.close()
|
|
2477
|
+
await browser.close()
|
|
2478
|
+
await page.close()
|
|
2479
|
+
}, 60000)
|
|
2480
|
+
|
|
2481
|
+
it('should pause on all exceptions with setPauseOnExceptions', async () => {
|
|
2482
|
+
const browserContext = getBrowserContext()
|
|
2483
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2484
|
+
|
|
2485
|
+
const page = await browserContext.newPage()
|
|
2486
|
+
await page.goto('https://example.com/')
|
|
2487
|
+
await page.bringToFront()
|
|
2488
|
+
|
|
2489
|
+
await serviceWorker.evaluate(async () => {
|
|
2490
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2491
|
+
})
|
|
2492
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2493
|
+
|
|
2494
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2495
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2496
|
+
expect(cdpPage).toBeDefined()
|
|
2497
|
+
|
|
2498
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2499
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2500
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2501
|
+
|
|
2502
|
+
await dbg.enable()
|
|
2503
|
+
await dbg.setPauseOnExceptions({ state: 'all' })
|
|
2504
|
+
|
|
2505
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
2506
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
2507
|
+
})
|
|
2508
|
+
|
|
2509
|
+
cdpPage!.evaluate(`
|
|
2510
|
+
(function() {
|
|
2511
|
+
try {
|
|
2512
|
+
throw new Error('Caught test error');
|
|
2513
|
+
} catch (e) {
|
|
2514
|
+
// caught but should still pause with state 'all'
|
|
2515
|
+
}
|
|
2516
|
+
})()
|
|
2517
|
+
`).catch(() => {})
|
|
2518
|
+
|
|
2519
|
+
await Promise.race([
|
|
2520
|
+
pausedPromise,
|
|
2521
|
+
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
|
|
2522
|
+
])
|
|
2523
|
+
|
|
2524
|
+
expect(dbg.isPaused()).toBe(true)
|
|
2525
|
+
|
|
2526
|
+
const location = await dbg.getLocation()
|
|
2527
|
+
expect(location.sourceContext).toContain('throw')
|
|
2528
|
+
|
|
2529
|
+
await dbg.resume()
|
|
2530
|
+
|
|
2531
|
+
await dbg.setPauseOnExceptions({ state: 'none' })
|
|
2532
|
+
|
|
2533
|
+
cdpSession.close()
|
|
2534
|
+
await browser.close()
|
|
2535
|
+
await page.close()
|
|
2536
|
+
}, 60000)
|
|
2537
|
+
|
|
2538
|
+
it('should inspect local and global variables with inline snapshots', async () => {
|
|
2539
|
+
const browserContext = getBrowserContext()
|
|
2540
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2541
|
+
|
|
2542
|
+
const page = await browserContext.newPage()
|
|
2543
|
+
await page.setContent(`
|
|
2544
|
+
<html>
|
|
2545
|
+
<head>
|
|
2546
|
+
<script>
|
|
2547
|
+
const GLOBAL_CONFIG = 'production';
|
|
2548
|
+
function runTest() {
|
|
2549
|
+
const userName = 'Alice';
|
|
2550
|
+
const userAge = 25;
|
|
2551
|
+
const settings = { theme: 'dark', lang: 'en' };
|
|
2552
|
+
const scores = [10, 20, 30];
|
|
2553
|
+
debugger;
|
|
2554
|
+
return userName;
|
|
2555
|
+
}
|
|
2556
|
+
</script>
|
|
2557
|
+
</head>
|
|
2558
|
+
<body>
|
|
2559
|
+
<button onclick="runTest()">Run</button>
|
|
2560
|
+
</body>
|
|
2561
|
+
</html>
|
|
2562
|
+
`)
|
|
2563
|
+
await page.bringToFront()
|
|
2564
|
+
|
|
2565
|
+
await serviceWorker.evaluate(async () => {
|
|
2566
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2567
|
+
})
|
|
2568
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2569
|
+
|
|
2570
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2571
|
+
let cdpPage
|
|
2572
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
2573
|
+
const html = await p.content()
|
|
2574
|
+
if (html.includes('runTest')) {
|
|
2575
|
+
cdpPage = p
|
|
2576
|
+
break
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
expect(cdpPage).toBeDefined()
|
|
2580
|
+
|
|
2581
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2582
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2583
|
+
const dbg = new Debugger({ cdp: cdpSession })
|
|
2584
|
+
|
|
2585
|
+
await dbg.enable()
|
|
2586
|
+
|
|
2587
|
+
const globalVars = await dbg.inspectGlobalVariables()
|
|
2588
|
+
expect(globalVars).toMatchInlineSnapshot(`
|
|
2589
|
+
[
|
|
2590
|
+
"GLOBAL_CONFIG",
|
|
2591
|
+
]
|
|
2592
|
+
`)
|
|
2593
|
+
|
|
2594
|
+
const pausedPromise = new Promise<void>((resolve) => {
|
|
2595
|
+
cdpSession.on('Debugger.paused', () => resolve())
|
|
2596
|
+
})
|
|
2597
|
+
|
|
2598
|
+
cdpPage!.evaluate('runTest()')
|
|
2599
|
+
|
|
2600
|
+
await pausedPromise
|
|
2601
|
+
expect(dbg.isPaused()).toBe(true)
|
|
2602
|
+
|
|
2603
|
+
const localVars = await dbg.inspectLocalVariables()
|
|
2604
|
+
expect(localVars).toMatchInlineSnapshot(`
|
|
2605
|
+
{
|
|
2606
|
+
"GLOBAL_CONFIG": "production",
|
|
2607
|
+
"scores": "[array]",
|
|
2608
|
+
"settings": "[object]",
|
|
2609
|
+
"userAge": 25,
|
|
2610
|
+
"userName": "Alice",
|
|
2611
|
+
}
|
|
2612
|
+
`)
|
|
2613
|
+
|
|
2614
|
+
await dbg.resume()
|
|
2615
|
+
|
|
2616
|
+
cdpSession.close()
|
|
2617
|
+
await browser.close()
|
|
2618
|
+
await page.close()
|
|
2619
|
+
}, 60000)
|
|
2620
|
+
|
|
2621
|
+
it('should click at correct coordinates on high-DPI simulation', async () => {
|
|
2622
|
+
const browserContext = getBrowserContext()
|
|
2623
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2624
|
+
|
|
2625
|
+
const page = await browserContext.newPage()
|
|
2626
|
+
await page.goto('https://example.com/')
|
|
2627
|
+
await page.bringToFront()
|
|
2628
|
+
|
|
2629
|
+
await serviceWorker.evaluate(async () => {
|
|
2630
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2631
|
+
})
|
|
2632
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2633
|
+
|
|
2634
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2635
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2636
|
+
expect(cdpPage).toBeDefined()
|
|
2637
|
+
|
|
2638
|
+
const h1Bounds = await cdpPage!.locator('h1').boundingBox()
|
|
2639
|
+
expect(h1Bounds).toBeDefined()
|
|
2640
|
+
console.log('H1 bounding box:', h1Bounds)
|
|
2641
|
+
|
|
2642
|
+
await cdpPage!.evaluate(() => {
|
|
2643
|
+
(window as any).clickedAt = null;
|
|
2644
|
+
document.addEventListener('click', (e) => {
|
|
2645
|
+
(window as any).clickedAt = { x: e.clientX, y: e.clientY };
|
|
2646
|
+
});
|
|
2647
|
+
})
|
|
2648
|
+
|
|
2649
|
+
await cdpPage!.locator('h1').click()
|
|
2650
|
+
|
|
2651
|
+
const clickedAt = await cdpPage!.evaluate(() => (window as any).clickedAt)
|
|
2017
2652
|
console.log('Clicked at:', clickedAt)
|
|
2018
2653
|
|
|
2019
2654
|
expect(clickedAt).toBeDefined()
|
|
@@ -2023,4 +2658,226 @@ describe('CDP Session Tests', () => {
|
|
|
2023
2658
|
await browser.close()
|
|
2024
2659
|
await page.close()
|
|
2025
2660
|
}, 60000)
|
|
2661
|
+
|
|
2662
|
+
it('should use Editor class to list, read, and edit scripts', async () => {
|
|
2663
|
+
const browserContext = getBrowserContext()
|
|
2664
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2665
|
+
|
|
2666
|
+
const page = await browserContext.newPage()
|
|
2667
|
+
await page.goto('https://example.com/')
|
|
2668
|
+
await page.bringToFront()
|
|
2669
|
+
|
|
2670
|
+
await serviceWorker.evaluate(async () => {
|
|
2671
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2672
|
+
})
|
|
2673
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2674
|
+
|
|
2675
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2676
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2677
|
+
expect(cdpPage).toBeDefined()
|
|
2678
|
+
|
|
2679
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2680
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2681
|
+
const editor = new Editor({ cdp: cdpSession })
|
|
2682
|
+
|
|
2683
|
+
await editor.enable()
|
|
2684
|
+
|
|
2685
|
+
await cdpPage!.addScriptTag({
|
|
2686
|
+
content: `
|
|
2687
|
+
function greetUser(name) {
|
|
2688
|
+
console.log('Hello, ' + name);
|
|
2689
|
+
return 'Hello, ' + name;
|
|
2690
|
+
}
|
|
2691
|
+
`,
|
|
2692
|
+
})
|
|
2693
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2694
|
+
const scripts = await editor.list()
|
|
2695
|
+
expect(scripts.length).toBeGreaterThan(0)
|
|
2696
|
+
|
|
2697
|
+
const matches = await editor.grep({ regex: /greetUser/ })
|
|
2698
|
+
expect(matches.length).toBeGreaterThan(0)
|
|
2699
|
+
|
|
2700
|
+
const match = matches[0]
|
|
2701
|
+
const { content, totalLines } = await editor.read({ url: match.url })
|
|
2702
|
+
expect(content).toContain('greetUser')
|
|
2703
|
+
expect(totalLines).toBeGreaterThan(0)
|
|
2704
|
+
|
|
2705
|
+
await editor.edit({
|
|
2706
|
+
url: match.url,
|
|
2707
|
+
oldString: "console.log('Hello, ' + name);",
|
|
2708
|
+
newString: "console.log('Hello, ' + name); console.log('EDITOR_TEST_MARKER');",
|
|
2709
|
+
})
|
|
2710
|
+
|
|
2711
|
+
const consoleLogs: string[] = []
|
|
2712
|
+
cdpPage!.on('console', msg => {
|
|
2713
|
+
consoleLogs.push(msg.text())
|
|
2714
|
+
})
|
|
2715
|
+
|
|
2716
|
+
await cdpPage!.evaluate(() => {
|
|
2717
|
+
(window as any).greetUser('World')
|
|
2718
|
+
})
|
|
2719
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2720
|
+
|
|
2721
|
+
expect(consoleLogs).toContain('Hello, World')
|
|
2722
|
+
expect(consoleLogs).toContain('EDITOR_TEST_MARKER')
|
|
2723
|
+
|
|
2724
|
+
cdpSession.close()
|
|
2725
|
+
await browser.close()
|
|
2726
|
+
await page.close()
|
|
2727
|
+
}, 60000)
|
|
2728
|
+
|
|
2729
|
+
it('editor can list, read, and edit CSS stylesheets', async () => {
|
|
2730
|
+
const browserContext = getBrowserContext()
|
|
2731
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2732
|
+
|
|
2733
|
+
const page = await browserContext.newPage()
|
|
2734
|
+
await page.goto('https://example.com/')
|
|
2735
|
+
await page.bringToFront()
|
|
2736
|
+
|
|
2737
|
+
await serviceWorker.evaluate(async () => {
|
|
2738
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2739
|
+
})
|
|
2740
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2741
|
+
|
|
2742
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2743
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'))
|
|
2744
|
+
expect(cdpPage).toBeDefined()
|
|
2745
|
+
|
|
2746
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2747
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2748
|
+
const editor = new Editor({ cdp: cdpSession })
|
|
2749
|
+
|
|
2750
|
+
await editor.enable()
|
|
2751
|
+
|
|
2752
|
+
await cdpPage!.addStyleTag({
|
|
2753
|
+
content: `
|
|
2754
|
+
.editor-test-element {
|
|
2755
|
+
color: rgb(255, 0, 0);
|
|
2756
|
+
background-color: rgb(0, 0, 255);
|
|
2757
|
+
}
|
|
2758
|
+
`,
|
|
2759
|
+
})
|
|
2760
|
+
await new Promise(r => setTimeout(r, 100))
|
|
2761
|
+
const stylesheets = await editor.list({ pattern: /inline-css:/ })
|
|
2762
|
+
expect(stylesheets.length).toBeGreaterThan(0)
|
|
2763
|
+
|
|
2764
|
+
const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ })
|
|
2765
|
+
expect(cssMatches.length).toBeGreaterThan(0)
|
|
2766
|
+
|
|
2767
|
+
const cssMatch = cssMatches[0]
|
|
2768
|
+
const { content, totalLines } = await editor.read({ url: cssMatch.url })
|
|
2769
|
+
expect(content).toContain('editor-test-element')
|
|
2770
|
+
expect(content).toContain('rgb(255, 0, 0)')
|
|
2771
|
+
expect(totalLines).toBeGreaterThan(0)
|
|
2772
|
+
|
|
2773
|
+
await cdpPage!.evaluate(() => {
|
|
2774
|
+
const el = document.createElement('div')
|
|
2775
|
+
el.className = 'editor-test-element'
|
|
2776
|
+
el.id = 'test-div'
|
|
2777
|
+
el.textContent = 'Test'
|
|
2778
|
+
document.body.appendChild(el)
|
|
2779
|
+
})
|
|
2780
|
+
|
|
2781
|
+
const colorBefore = await cdpPage!.evaluate(() => {
|
|
2782
|
+
const el = document.getElementById('test-div')!
|
|
2783
|
+
return window.getComputedStyle(el).color
|
|
2784
|
+
})
|
|
2785
|
+
expect(colorBefore).toBe('rgb(255, 0, 0)')
|
|
2786
|
+
|
|
2787
|
+
await editor.edit({
|
|
2788
|
+
url: cssMatch.url,
|
|
2789
|
+
oldString: 'color: rgb(255, 0, 0);',
|
|
2790
|
+
newString: 'color: rgb(0, 255, 0);',
|
|
2791
|
+
})
|
|
2792
|
+
|
|
2793
|
+
const colorAfter = await cdpPage!.evaluate(() => {
|
|
2794
|
+
const el = document.getElementById('test-div')!
|
|
2795
|
+
return window.getComputedStyle(el).color
|
|
2796
|
+
})
|
|
2797
|
+
expect(colorAfter).toBe('rgb(0, 255, 0)')
|
|
2798
|
+
|
|
2799
|
+
cdpSession.close()
|
|
2800
|
+
await browser.close()
|
|
2801
|
+
await page.close()
|
|
2802
|
+
}, 60000)
|
|
2803
|
+
|
|
2804
|
+
it('should inject bippy and find React fiber with getReactSource', async () => {
|
|
2805
|
+
const browserContext = getBrowserContext()
|
|
2806
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2807
|
+
|
|
2808
|
+
const page = await browserContext.newPage()
|
|
2809
|
+
await page.setContent(`
|
|
2810
|
+
<!DOCTYPE html>
|
|
2811
|
+
<html>
|
|
2812
|
+
<head>
|
|
2813
|
+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
2814
|
+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
2815
|
+
</head>
|
|
2816
|
+
<body>
|
|
2817
|
+
<div id="root"></div>
|
|
2818
|
+
<script>
|
|
2819
|
+
function MyComponent() {
|
|
2820
|
+
return React.createElement('button', { id: 'react-btn' }, 'Click me');
|
|
2821
|
+
}
|
|
2822
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
2823
|
+
root.render(React.createElement(MyComponent));
|
|
2824
|
+
</script>
|
|
2825
|
+
</body>
|
|
2826
|
+
</html>
|
|
2827
|
+
`)
|
|
2828
|
+
await page.bringToFront()
|
|
2829
|
+
|
|
2830
|
+
await serviceWorker.evaluate(async () => {
|
|
2831
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2832
|
+
})
|
|
2833
|
+
await new Promise(r => setTimeout(r, 500))
|
|
2834
|
+
|
|
2835
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2836
|
+
const pages = browser.contexts()[0].pages()
|
|
2837
|
+
const cdpPage = pages.find(p => p.url().startsWith('about:'))
|
|
2838
|
+
expect(cdpPage).toBeDefined()
|
|
2839
|
+
|
|
2840
|
+
const btn = cdpPage!.locator('#react-btn')
|
|
2841
|
+
const btnCount = await btn.count()
|
|
2842
|
+
expect(btnCount).toBe(1)
|
|
2843
|
+
|
|
2844
|
+
const hasBippyBefore = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
|
2845
|
+
expect(hasBippyBefore).toBe(false)
|
|
2846
|
+
|
|
2847
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
|
2848
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
|
2849
|
+
|
|
2850
|
+
const { getReactSource } = await import('./react-source.js')
|
|
2851
|
+
const source = await getReactSource({ locator: btn, cdp: cdpSession })
|
|
2852
|
+
|
|
2853
|
+
const hasBippyAfter = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
|
2854
|
+
expect(hasBippyAfter).toBe(true)
|
|
2855
|
+
|
|
2856
|
+
const hasFiber = await btn.evaluate((el) => {
|
|
2857
|
+
const bippy = (globalThis as any).__bippy
|
|
2858
|
+
const fiber = bippy.getFiberFromHostInstance(el)
|
|
2859
|
+
return !!fiber
|
|
2860
|
+
})
|
|
2861
|
+
expect(hasFiber).toBe(true)
|
|
2862
|
+
|
|
2863
|
+
const componentName = await btn.evaluate((el) => {
|
|
2864
|
+
const bippy = (globalThis as any).__bippy
|
|
2865
|
+
const fiber = bippy.getFiberFromHostInstance(el)
|
|
2866
|
+
let current = fiber
|
|
2867
|
+
while (current) {
|
|
2868
|
+
if (bippy.isCompositeFiber(current)) {
|
|
2869
|
+
return bippy.getDisplayName(current.type)
|
|
2870
|
+
}
|
|
2871
|
+
current = current.return
|
|
2872
|
+
}
|
|
2873
|
+
return null
|
|
2874
|
+
})
|
|
2875
|
+
expect(componentName).toBe('MyComponent')
|
|
2876
|
+
|
|
2877
|
+
console.log('Component name from fiber:', componentName)
|
|
2878
|
+
console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source)
|
|
2879
|
+
|
|
2880
|
+
await browser.close()
|
|
2881
|
+
await page.close()
|
|
2882
|
+
}, 60000)
|
|
2026
2883
|
})
|