playwriter 0.0.25 → 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/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +2 -1
- package/dist/create-logger.js.map +1 -1
- 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 -27
- 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 +5 -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/create-logger.ts +2 -1
- 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/dist/mcp.test.js
CHANGED
|
@@ -9,9 +9,12 @@ import os from 'node:os';
|
|
|
9
9
|
import { getCdpUrl } from './utils.js';
|
|
10
10
|
import { imageSize } from 'image-size';
|
|
11
11
|
import { getCDPSessionForPage } from './cdp-session.js';
|
|
12
|
-
import {
|
|
12
|
+
import { Debugger } from './debugger.js';
|
|
13
|
+
import { Editor } from './editor.js';
|
|
14
|
+
import { startPlayWriterCDPRelayServer } from './cdp-relay.js';
|
|
13
15
|
import { createFileLogger } from './create-logger.js';
|
|
14
16
|
import { killPortProcess } from 'kill-port-process';
|
|
17
|
+
const TEST_PORT = 19987;
|
|
15
18
|
const execAsync = promisify(exec);
|
|
16
19
|
async function getExtensionServiceWorker(context) {
|
|
17
20
|
let serviceWorkers = context.serviceWorkers().filter(sw => sw.url().startsWith('chrome-extension://'));
|
|
@@ -45,13 +48,13 @@ async function killProcessOnPort(port) {
|
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
async function setupTestContext({ tempDirPrefix }) {
|
|
48
|
-
await killProcessOnPort(
|
|
51
|
+
await killProcessOnPort(TEST_PORT);
|
|
49
52
|
console.log('Building extension...');
|
|
50
|
-
await execAsync(
|
|
53
|
+
await execAsync(`TESTING=1 PLAYWRITER_PORT=${TEST_PORT} pnpm build`, { cwd: '../extension' });
|
|
51
54
|
console.log('Extension built');
|
|
52
55
|
const localLogPath = path.join(process.cwd(), 'relay-server.log');
|
|
53
56
|
const logger = createFileLogger({ logFilePath: localLogPath });
|
|
54
|
-
const relayServer = await startPlayWriterCDPRelayServer({ port:
|
|
57
|
+
const relayServer = await startPlayWriterCDPRelayServer({ port: TEST_PORT, logger });
|
|
55
58
|
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix));
|
|
56
59
|
const extensionPath = path.resolve('../extension/dist');
|
|
57
60
|
const browserContext = await chromium.launchPersistentContext(userDataDir, {
|
|
@@ -96,7 +99,7 @@ describe('MCP Server Tests', () => {
|
|
|
96
99
|
let testCtx = null;
|
|
97
100
|
beforeAll(async () => {
|
|
98
101
|
testCtx = await setupTestContext({ tempDirPrefix: 'pw-test-' });
|
|
99
|
-
const result = await createMCPClient();
|
|
102
|
+
const result = await createMCPClient({ port: TEST_PORT });
|
|
100
103
|
client = result.client;
|
|
101
104
|
cleanup = result.cleanup;
|
|
102
105
|
}, 600000);
|
|
@@ -119,8 +122,8 @@ describe('MCP Server Tests', () => {
|
|
|
119
122
|
await serviceWorker.evaluate(async () => {
|
|
120
123
|
await globalThis.toggleExtensionForActiveTab();
|
|
121
124
|
});
|
|
122
|
-
await new Promise(r => setTimeout(r,
|
|
123
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
125
|
+
await new Promise(r => setTimeout(r, 100));
|
|
126
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
124
127
|
const cdpPage = browser.contexts()[0].pages().find(p => {
|
|
125
128
|
return p.url().startsWith('about:');
|
|
126
129
|
});
|
|
@@ -149,7 +152,7 @@ describe('MCP Server Tests', () => {
|
|
|
149
152
|
name: 'execute',
|
|
150
153
|
arguments: {
|
|
151
154
|
code: js `
|
|
152
|
-
await state.page.goto('https://
|
|
155
|
+
await state.page.goto('https://example.com');
|
|
153
156
|
const title = await state.page.title();
|
|
154
157
|
console.log('Page title:', title);
|
|
155
158
|
return { url: state.page.url(), title };
|
|
@@ -160,12 +163,12 @@ describe('MCP Server Tests', () => {
|
|
|
160
163
|
[
|
|
161
164
|
{
|
|
162
165
|
"text": "Console output:
|
|
163
|
-
[log] Page title:
|
|
166
|
+
[log] Page title: Example Domain
|
|
164
167
|
|
|
165
168
|
Return value:
|
|
166
169
|
{
|
|
167
|
-
"url": "https://
|
|
168
|
-
"title": "
|
|
170
|
+
\"url\": \"https://example.com/\",
|
|
171
|
+
\"title\": \"Example Domain\"
|
|
169
172
|
}",
|
|
170
173
|
"type": "text",
|
|
171
174
|
},
|
|
@@ -232,7 +235,7 @@ describe('MCP Server Tests', () => {
|
|
|
232
235
|
name: 'execute',
|
|
233
236
|
arguments: {
|
|
234
237
|
code: js `
|
|
235
|
-
await state.page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: '
|
|
238
|
+
await state.page.goto('https://news.ycombinator.com/item?id=1', { waitUntil: 'domcontentloaded' });
|
|
236
239
|
const snapshot = await state.page._snapshotForAI();
|
|
237
240
|
return snapshot;
|
|
238
241
|
`,
|
|
@@ -262,7 +265,7 @@ describe('MCP Server Tests', () => {
|
|
|
262
265
|
name: 'execute',
|
|
263
266
|
arguments: {
|
|
264
267
|
code: js `
|
|
265
|
-
await state.page.goto('https://ui.shadcn.com/', { waitUntil: '
|
|
268
|
+
await state.page.goto('https://ui.shadcn.com/', { waitUntil: 'domcontentloaded' });
|
|
266
269
|
const snapshot = await state.page._snapshotForAI();
|
|
267
270
|
return snapshot;
|
|
268
271
|
`,
|
|
@@ -308,7 +311,7 @@ describe('MCP Server Tests', () => {
|
|
|
308
311
|
});
|
|
309
312
|
expect(result.isConnected).toBe(true);
|
|
310
313
|
// 3. Verify we can connect via direct CDP and see the page
|
|
311
|
-
let directBrowser = await chromium.connectOverCDP(getCdpUrl());
|
|
314
|
+
let directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
312
315
|
let contexts = directBrowser.contexts();
|
|
313
316
|
let pages = contexts[0].pages();
|
|
314
317
|
// Find our page
|
|
@@ -327,7 +330,7 @@ describe('MCP Server Tests', () => {
|
|
|
327
330
|
// 5. Try to connect/use the page.
|
|
328
331
|
// connecting to relay will succeed, but listing pages should NOT show our page
|
|
329
332
|
// Connect to relay again
|
|
330
|
-
directBrowser = await chromium.connectOverCDP(getCdpUrl());
|
|
333
|
+
directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
331
334
|
contexts = directBrowser.contexts();
|
|
332
335
|
pages = contexts[0].pages();
|
|
333
336
|
foundPage = pages.find(p => p.url() === testUrl);
|
|
@@ -339,13 +342,13 @@ describe('MCP Server Tests', () => {
|
|
|
339
342
|
});
|
|
340
343
|
expect(resultEnabled.isConnected).toBe(true);
|
|
341
344
|
// 7. Verify page is back
|
|
342
|
-
directBrowser = await chromium.connectOverCDP(getCdpUrl());
|
|
345
|
+
directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
343
346
|
// Wait a bit for targets to populate
|
|
344
|
-
await new Promise(r => setTimeout(r,
|
|
347
|
+
await new Promise(r => setTimeout(r, 100));
|
|
345
348
|
contexts = directBrowser.contexts();
|
|
346
349
|
// pages() might need a moment if target attached event comes in
|
|
347
350
|
if (contexts[0].pages().length === 0) {
|
|
348
|
-
await new Promise(r => setTimeout(r,
|
|
351
|
+
await new Promise(r => setTimeout(r, 100));
|
|
349
352
|
}
|
|
350
353
|
pages = contexts[0].pages();
|
|
351
354
|
foundPage = pages.find(p => p.url() === testUrl);
|
|
@@ -361,9 +364,9 @@ describe('MCP Server Tests', () => {
|
|
|
361
364
|
const browserContext = getBrowserContext();
|
|
362
365
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
363
366
|
// Connect once
|
|
364
|
-
const directBrowser = await chromium.connectOverCDP(getCdpUrl());
|
|
367
|
+
const directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
365
368
|
// Wait a bit for connection and initial target discovery
|
|
366
|
-
await new Promise(r => setTimeout(r,
|
|
369
|
+
await new Promise(r => setTimeout(r, 100));
|
|
367
370
|
// 1. Create a new page
|
|
368
371
|
const page = await browserContext.newPage();
|
|
369
372
|
const testUrl = 'https://example.com/persistent';
|
|
@@ -433,7 +436,7 @@ describe('MCP Server Tests', () => {
|
|
|
433
436
|
await globalThis.toggleExtensionForActiveTab();
|
|
434
437
|
});
|
|
435
438
|
// 3. Connect via CDP
|
|
436
|
-
const cdpUrl = getCdpUrl();
|
|
439
|
+
const cdpUrl = getCdpUrl({ port: TEST_PORT });
|
|
437
440
|
const directBrowser = await chromium.connectOverCDP(cdpUrl);
|
|
438
441
|
const connectedPage = directBrowser.contexts()[0].pages().find(p => p.url() === initialUrl);
|
|
439
442
|
expect(connectedPage).toBeDefined();
|
|
@@ -443,16 +446,16 @@ describe('MCP Server Tests', () => {
|
|
|
443
446
|
// We use a loop to check if it's still connected because reload might cause temporary disconnect/reconnect events
|
|
444
447
|
// that Playwright handles natively if the session ID stays valid.
|
|
445
448
|
await connectedPage?.reload();
|
|
446
|
-
await connectedPage?.waitForLoadState('
|
|
449
|
+
await connectedPage?.waitForLoadState('domcontentloaded');
|
|
447
450
|
expect(await connectedPage?.title()).toBe('Example Domain');
|
|
448
451
|
// Verify execution after reload
|
|
449
452
|
expect(await connectedPage?.evaluate(() => 2 + 2)).toBe(4);
|
|
450
453
|
// 5. Navigate to new URL
|
|
451
|
-
const newUrl = 'https://
|
|
454
|
+
const newUrl = 'https://example.org/';
|
|
452
455
|
await connectedPage?.goto(newUrl);
|
|
453
|
-
await connectedPage?.waitForLoadState('
|
|
456
|
+
await connectedPage?.waitForLoadState('domcontentloaded');
|
|
454
457
|
expect(connectedPage?.url()).toBe(newUrl);
|
|
455
|
-
expect(await connectedPage?.title()).toContain('
|
|
458
|
+
expect(await connectedPage?.title()).toContain('Example Domain');
|
|
456
459
|
// Verify execution after navigation
|
|
457
460
|
expect(await connectedPage?.evaluate(() => 3 + 3)).toBe(6);
|
|
458
461
|
await directBrowser.close();
|
|
@@ -461,12 +464,12 @@ describe('MCP Server Tests', () => {
|
|
|
461
464
|
it('should support multiple concurrent tabs', async () => {
|
|
462
465
|
const browserContext = getBrowserContext();
|
|
463
466
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
464
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
467
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
465
468
|
// Tab A
|
|
466
469
|
const pageA = await browserContext.newPage();
|
|
467
470
|
await pageA.goto('https://example.com/tab-a');
|
|
468
471
|
await pageA.bringToFront();
|
|
469
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
472
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
470
473
|
await serviceWorker.evaluate(async () => {
|
|
471
474
|
await globalThis.toggleExtensionForActiveTab();
|
|
472
475
|
});
|
|
@@ -474,7 +477,7 @@ describe('MCP Server Tests', () => {
|
|
|
474
477
|
const pageB = await browserContext.newPage();
|
|
475
478
|
await pageB.goto('https://example.com/tab-b');
|
|
476
479
|
await pageB.bringToFront();
|
|
477
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
480
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
478
481
|
await serviceWorker.evaluate(async () => {
|
|
479
482
|
await globalThis.toggleExtensionForActiveTab();
|
|
480
483
|
});
|
|
@@ -501,7 +504,7 @@ describe('MCP Server Tests', () => {
|
|
|
501
504
|
`);
|
|
502
505
|
expect(targetIds.idA).not.toBe(targetIds.idB);
|
|
503
506
|
// Verify independent connections
|
|
504
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
507
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
505
508
|
const pages = browser.contexts()[0].pages();
|
|
506
509
|
const results = await Promise.all(pages.map(async (p) => ({
|
|
507
510
|
url: p.url(),
|
|
@@ -540,15 +543,15 @@ describe('MCP Server Tests', () => {
|
|
|
540
543
|
await page.goto(targetUrl);
|
|
541
544
|
await page.bringToFront();
|
|
542
545
|
// Wait for load
|
|
543
|
-
await page.waitForLoadState('
|
|
546
|
+
await page.waitForLoadState('domcontentloaded');
|
|
544
547
|
// 2. Enable extension for this page
|
|
545
548
|
await serviceWorker.evaluate(async () => {
|
|
546
549
|
await globalThis.toggleExtensionForActiveTab();
|
|
547
550
|
});
|
|
548
551
|
// 3. Verify via CDP that the correct URL is shown
|
|
549
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
552
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
550
553
|
// Wait for sync
|
|
551
|
-
await new Promise(r => setTimeout(r,
|
|
554
|
+
await new Promise(r => setTimeout(r, 100));
|
|
552
555
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url() === targetUrl);
|
|
553
556
|
expect(cdpPage).toBeDefined();
|
|
554
557
|
expect(cdpPage?.url()).toBe(targetUrl);
|
|
@@ -562,7 +565,7 @@ describe('MCP Server Tests', () => {
|
|
|
562
565
|
expect(pages.length).toBeGreaterThan(0);
|
|
563
566
|
const page = pages[0];
|
|
564
567
|
await page.goto('https://example.com/disconnect-test');
|
|
565
|
-
await page.waitForLoadState('
|
|
568
|
+
await page.waitForLoadState('domcontentloaded');
|
|
566
569
|
await page.bringToFront();
|
|
567
570
|
// Enable extension on this page
|
|
568
571
|
const initialEnable = await serviceWorker.evaluate(async () => {
|
|
@@ -571,7 +574,7 @@ describe('MCP Server Tests', () => {
|
|
|
571
574
|
console.log('Initial enable result:', initialEnable);
|
|
572
575
|
expect(initialEnable.isConnected).toBe(true);
|
|
573
576
|
// Wait for extension to fully connect
|
|
574
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
577
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
575
578
|
// Verify MCP can see the page
|
|
576
579
|
const beforeDisconnect = await client.callTool({
|
|
577
580
|
name: 'execute',
|
|
@@ -594,7 +597,7 @@ describe('MCP Server Tests', () => {
|
|
|
594
597
|
await globalThis.disconnectEverything();
|
|
595
598
|
});
|
|
596
599
|
// Wait for disconnect to complete
|
|
597
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
600
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
598
601
|
// 3. Verify MCP cannot see the page anymore
|
|
599
602
|
const afterDisconnect = await client.callTool({
|
|
600
603
|
name: 'execute',
|
|
@@ -622,7 +625,7 @@ describe('MCP Server Tests', () => {
|
|
|
622
625
|
expect(reconnectResult.isConnected).toBe(true);
|
|
623
626
|
// Wait for extension to fully reconnect and relay server to be ready
|
|
624
627
|
console.log('Waiting for reconnection to stabilize...');
|
|
625
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
628
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
626
629
|
// 5. Reset the MCP client's playwright connection since it was closed by disconnectEverything
|
|
627
630
|
console.log('Resetting MCP playwright connection...');
|
|
628
631
|
const resetResult = await client.callTool({
|
|
@@ -985,19 +988,19 @@ describe('MCP Server Tests', () => {
|
|
|
985
988
|
const browserContext = getBrowserContext();
|
|
986
989
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
987
990
|
const page = await browserContext.newPage();
|
|
988
|
-
const targetUrl = 'https://
|
|
989
|
-
await page.goto(targetUrl
|
|
991
|
+
const targetUrl = 'https://example.com/sw-test';
|
|
992
|
+
await page.goto(targetUrl);
|
|
990
993
|
await page.bringToFront();
|
|
991
994
|
await serviceWorker.evaluate(async () => {
|
|
992
995
|
await globalThis.toggleExtensionForActiveTab();
|
|
993
996
|
});
|
|
994
|
-
await new Promise(r => setTimeout(r,
|
|
995
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
997
|
+
await new Promise(r => setTimeout(r, 100));
|
|
998
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
996
999
|
const pages = browser.contexts()[0].pages();
|
|
997
|
-
const
|
|
998
|
-
expect(
|
|
999
|
-
expect(
|
|
1000
|
-
expect(
|
|
1000
|
+
const testPage = pages.find(p => p.url().includes('sw-test'));
|
|
1001
|
+
expect(testPage).toBeDefined();
|
|
1002
|
+
expect(testPage?.url()).toContain('sw-test');
|
|
1003
|
+
expect(testPage?.url()).not.toContain('sw.js');
|
|
1001
1004
|
await browser.close();
|
|
1002
1005
|
await page.close();
|
|
1003
1006
|
}, 30000);
|
|
@@ -1012,13 +1015,13 @@ describe('MCP Server Tests', () => {
|
|
|
1012
1015
|
await globalThis.toggleExtensionForActiveTab();
|
|
1013
1016
|
});
|
|
1014
1017
|
for (let i = 0; i < 5; i++) {
|
|
1015
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1018
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1016
1019
|
const pages = browser.contexts()[0].pages();
|
|
1017
1020
|
const testPage = pages.find(p => p.url().includes('repeated-test'));
|
|
1018
1021
|
expect(testPage).toBeDefined();
|
|
1019
1022
|
expect(testPage?.url()).toBe(targetUrl);
|
|
1020
1023
|
await browser.close();
|
|
1021
|
-
await new Promise(r => setTimeout(r,
|
|
1024
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1022
1025
|
}
|
|
1023
1026
|
await page.close();
|
|
1024
1027
|
}, 30000);
|
|
@@ -1032,7 +1035,7 @@ describe('MCP Server Tests', () => {
|
|
|
1032
1035
|
await serviceWorker.evaluate(async () => {
|
|
1033
1036
|
await globalThis.toggleExtensionForActiveTab();
|
|
1034
1037
|
});
|
|
1035
|
-
await new Promise(r => setTimeout(r,
|
|
1038
|
+
await new Promise(r => setTimeout(r, 400));
|
|
1036
1039
|
const [mcpResult, cdpBrowser] = await Promise.all([
|
|
1037
1040
|
client.callTool({
|
|
1038
1041
|
name: 'execute',
|
|
@@ -1044,7 +1047,7 @@ describe('MCP Server Tests', () => {
|
|
|
1044
1047
|
`,
|
|
1045
1048
|
},
|
|
1046
1049
|
}),
|
|
1047
|
-
chromium.connectOverCDP(getCdpUrl())
|
|
1050
|
+
chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1048
1051
|
]);
|
|
1049
1052
|
const mcpOutput = mcpResult.content[0].text;
|
|
1050
1053
|
expect(mcpOutput).toContain(targetUrl);
|
|
@@ -1058,24 +1061,40 @@ describe('MCP Server Tests', () => {
|
|
|
1058
1061
|
const browserContext = getBrowserContext();
|
|
1059
1062
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1060
1063
|
const page = await browserContext.newPage();
|
|
1061
|
-
|
|
1062
|
-
|
|
1064
|
+
await page.setContent(`
|
|
1065
|
+
<html>
|
|
1066
|
+
<head><title>Iframe Test Page</title></head>
|
|
1067
|
+
<body>
|
|
1068
|
+
<h1>Iframe Heavy Page</h1>
|
|
1069
|
+
<iframe src="about:blank" id="frame1"></iframe>
|
|
1070
|
+
<iframe src="about:blank" id="frame2"></iframe>
|
|
1071
|
+
<iframe src="about:blank" id="frame3"></iframe>
|
|
1072
|
+
</body>
|
|
1073
|
+
</html>
|
|
1074
|
+
`);
|
|
1063
1075
|
await page.bringToFront();
|
|
1064
1076
|
await serviceWorker.evaluate(async () => {
|
|
1065
1077
|
await globalThis.toggleExtensionForActiveTab();
|
|
1066
1078
|
});
|
|
1067
|
-
await new Promise(r => setTimeout(r,
|
|
1079
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1068
1080
|
for (let i = 0; i < 3; i++) {
|
|
1069
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1081
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1070
1082
|
const pages = browser.contexts()[0].pages();
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1083
|
+
let iframePage;
|
|
1084
|
+
for (const p of pages) {
|
|
1085
|
+
const html = await p.content();
|
|
1086
|
+
if (html.includes('Iframe Heavy Page')) {
|
|
1087
|
+
iframePage = p;
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
expect(iframePage).toBeDefined();
|
|
1092
|
+
expect(iframePage?.url()).toContain('about:');
|
|
1074
1093
|
await browser.close();
|
|
1075
|
-
await new Promise(r => setTimeout(r,
|
|
1094
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1076
1095
|
}
|
|
1077
1096
|
await page.close();
|
|
1078
|
-
},
|
|
1097
|
+
}, 30000);
|
|
1079
1098
|
it('should capture screenshot correctly', async () => {
|
|
1080
1099
|
const browserContext = getBrowserContext();
|
|
1081
1100
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
@@ -1085,7 +1104,7 @@ describe('MCP Server Tests', () => {
|
|
|
1085
1104
|
await serviceWorker.evaluate(async () => {
|
|
1086
1105
|
await globalThis.toggleExtensionForActiveTab();
|
|
1087
1106
|
});
|
|
1088
|
-
await new Promise(r => setTimeout(r,
|
|
1107
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1089
1108
|
const capturedCommands = [];
|
|
1090
1109
|
const commandHandler = ({ command }) => {
|
|
1091
1110
|
if (command.method === 'Page.captureScreenshot') {
|
|
@@ -1093,7 +1112,7 @@ describe('MCP Server Tests', () => {
|
|
|
1093
1112
|
}
|
|
1094
1113
|
};
|
|
1095
1114
|
testCtx.relayServer.on('cdp:command', commandHandler);
|
|
1096
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1115
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1097
1116
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1098
1117
|
expect(cdpPage).toBeDefined();
|
|
1099
1118
|
const viewportSize = cdpPage.viewportSize();
|
|
@@ -1197,7 +1216,7 @@ describe('MCP Server Tests', () => {
|
|
|
1197
1216
|
await serviceWorker.evaluate(async () => {
|
|
1198
1217
|
await globalThis.toggleExtensionForActiveTab();
|
|
1199
1218
|
});
|
|
1200
|
-
await new Promise(r => setTimeout(r,
|
|
1219
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1201
1220
|
const capturedCommands = [];
|
|
1202
1221
|
const commandHandler = ({ command }) => {
|
|
1203
1222
|
if (command.method === 'Page.captureScreenshot') {
|
|
@@ -1205,7 +1224,7 @@ describe('MCP Server Tests', () => {
|
|
|
1205
1224
|
}
|
|
1206
1225
|
};
|
|
1207
1226
|
testCtx.relayServer.on('cdp:command', commandHandler);
|
|
1208
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1227
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1209
1228
|
let cdpPage;
|
|
1210
1229
|
for (const p of browser.contexts()[0].pages()) {
|
|
1211
1230
|
const html = await p.content();
|
|
@@ -1252,7 +1271,7 @@ describe('MCP Server Tests', () => {
|
|
|
1252
1271
|
await serviceWorker.evaluate(async () => {
|
|
1253
1272
|
await globalThis.toggleExtensionForActiveTab();
|
|
1254
1273
|
});
|
|
1255
|
-
await new Promise(r => setTimeout(r,
|
|
1274
|
+
await new Promise(r => setTimeout(r, 400));
|
|
1256
1275
|
const result = await client.callTool({
|
|
1257
1276
|
name: 'execute',
|
|
1258
1277
|
arguments: {
|
|
@@ -1283,6 +1302,184 @@ describe('MCP Server Tests', () => {
|
|
|
1283
1302
|
expect(text).toContain('Locator text: Click Me');
|
|
1284
1303
|
await page.close();
|
|
1285
1304
|
}, 60000);
|
|
1305
|
+
it('should get styles for element using getStylesForLocator', async () => {
|
|
1306
|
+
const browserContext = getBrowserContext();
|
|
1307
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1308
|
+
const page = await browserContext.newPage();
|
|
1309
|
+
await page.setContent(`
|
|
1310
|
+
<html>
|
|
1311
|
+
<head>
|
|
1312
|
+
<style>
|
|
1313
|
+
body { font-family: Arial, sans-serif; color: #333; }
|
|
1314
|
+
.container { padding: 20px; margin: 10px; }
|
|
1315
|
+
#main-btn { background-color: blue; color: white; border-radius: 4px; }
|
|
1316
|
+
.btn { padding: 8px 16px; }
|
|
1317
|
+
</style>
|
|
1318
|
+
</head>
|
|
1319
|
+
<body>
|
|
1320
|
+
<div class="container">
|
|
1321
|
+
<button id="main-btn" class="btn" style="font-weight: bold;">Click Me</button>
|
|
1322
|
+
</div>
|
|
1323
|
+
</body>
|
|
1324
|
+
</html>
|
|
1325
|
+
`);
|
|
1326
|
+
await page.bringToFront();
|
|
1327
|
+
await serviceWorker.evaluate(async () => {
|
|
1328
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1329
|
+
});
|
|
1330
|
+
await new Promise(r => setTimeout(r, 400));
|
|
1331
|
+
const stylesResult = await client.callTool({
|
|
1332
|
+
name: 'execute',
|
|
1333
|
+
arguments: {
|
|
1334
|
+
code: js `
|
|
1335
|
+
let testPage;
|
|
1336
|
+
for (const p of context.pages()) {
|
|
1337
|
+
const html = await p.content();
|
|
1338
|
+
if (html.includes('main-btn')) { testPage = p; break; }
|
|
1339
|
+
}
|
|
1340
|
+
if (!testPage) throw new Error('Test page not found');
|
|
1341
|
+
const btn = testPage.locator('#main-btn');
|
|
1342
|
+
const styles = await getStylesForLocator({ locator: btn });
|
|
1343
|
+
return styles;
|
|
1344
|
+
`,
|
|
1345
|
+
timeout: 30000,
|
|
1346
|
+
},
|
|
1347
|
+
});
|
|
1348
|
+
expect(stylesResult.isError).toBeFalsy();
|
|
1349
|
+
const stylesText = stylesResult.content[0]?.text || '';
|
|
1350
|
+
expect(stylesText).toMatchInlineSnapshot(`
|
|
1351
|
+
"Return value:
|
|
1352
|
+
{
|
|
1353
|
+
"element": "button#main-btn.btn",
|
|
1354
|
+
"inlineStyle": {
|
|
1355
|
+
"font-weight": "bold"
|
|
1356
|
+
},
|
|
1357
|
+
"rules": [
|
|
1358
|
+
{
|
|
1359
|
+
"selector": ".btn",
|
|
1360
|
+
"source": null,
|
|
1361
|
+
"origin": "regular",
|
|
1362
|
+
"declarations": {
|
|
1363
|
+
"padding": "8px 16px",
|
|
1364
|
+
"padding-top": "8px",
|
|
1365
|
+
"padding-right": "16px",
|
|
1366
|
+
"padding-bottom": "8px",
|
|
1367
|
+
"padding-left": "16px"
|
|
1368
|
+
},
|
|
1369
|
+
"inheritedFrom": null
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
"selector": "#main-btn",
|
|
1373
|
+
"source": null,
|
|
1374
|
+
"origin": "regular",
|
|
1375
|
+
"declarations": {
|
|
1376
|
+
"background-color": "blue",
|
|
1377
|
+
"color": "white",
|
|
1378
|
+
"border-radius": "4px",
|
|
1379
|
+
"border-top-left-radius": "4px",
|
|
1380
|
+
"border-top-right-radius": "4px",
|
|
1381
|
+
"border-bottom-right-radius": "4px",
|
|
1382
|
+
"border-bottom-left-radius": "4px"
|
|
1383
|
+
},
|
|
1384
|
+
"inheritedFrom": null
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
"selector": ".container",
|
|
1388
|
+
"source": null,
|
|
1389
|
+
"origin": "regular",
|
|
1390
|
+
"declarations": {
|
|
1391
|
+
"padding": "20px",
|
|
1392
|
+
"margin": "10px",
|
|
1393
|
+
"padding-top": "20px",
|
|
1394
|
+
"padding-right": "20px",
|
|
1395
|
+
"padding-bottom": "20px",
|
|
1396
|
+
"padding-left": "20px",
|
|
1397
|
+
"margin-top": "10px",
|
|
1398
|
+
"margin-right": "10px",
|
|
1399
|
+
"margin-bottom": "10px",
|
|
1400
|
+
"margin-left": "10px"
|
|
1401
|
+
},
|
|
1402
|
+
"inheritedFrom": "ancestor[1]"
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
"selector": "body",
|
|
1406
|
+
"source": null,
|
|
1407
|
+
"origin": "regular",
|
|
1408
|
+
"declarations": {
|
|
1409
|
+
"font-family": "Arial, sans-serif",
|
|
1410
|
+
"color": "rgb(51, 51, 51)"
|
|
1411
|
+
},
|
|
1412
|
+
"inheritedFrom": "ancestor[2]"
|
|
1413
|
+
}
|
|
1414
|
+
]
|
|
1415
|
+
}"
|
|
1416
|
+
`);
|
|
1417
|
+
const formattedResult = await client.callTool({
|
|
1418
|
+
name: 'execute',
|
|
1419
|
+
arguments: {
|
|
1420
|
+
code: js `
|
|
1421
|
+
let testPage;
|
|
1422
|
+
for (const p of context.pages()) {
|
|
1423
|
+
const html = await p.content();
|
|
1424
|
+
if (html.includes('main-btn')) { testPage = p; break; }
|
|
1425
|
+
}
|
|
1426
|
+
if (!testPage) throw new Error('Test page not found');
|
|
1427
|
+
const btn = testPage.locator('#main-btn');
|
|
1428
|
+
const styles = await getStylesForLocator({ locator: btn });
|
|
1429
|
+
return formatStylesAsText(styles);
|
|
1430
|
+
`,
|
|
1431
|
+
timeout: 30000,
|
|
1432
|
+
},
|
|
1433
|
+
});
|
|
1434
|
+
expect(formattedResult.isError).toBeFalsy();
|
|
1435
|
+
const formattedText = formattedResult.content[0]?.text || '';
|
|
1436
|
+
expect(formattedText).toMatchInlineSnapshot(`
|
|
1437
|
+
"Return value:
|
|
1438
|
+
Element: button#main-btn.btn
|
|
1439
|
+
|
|
1440
|
+
Inline styles:
|
|
1441
|
+
font-weight: bold
|
|
1442
|
+
|
|
1443
|
+
Matched rules:
|
|
1444
|
+
.btn {
|
|
1445
|
+
padding: 8px 16px;
|
|
1446
|
+
padding-top: 8px;
|
|
1447
|
+
padding-right: 16px;
|
|
1448
|
+
padding-bottom: 8px;
|
|
1449
|
+
padding-left: 16px;
|
|
1450
|
+
}
|
|
1451
|
+
#main-btn {
|
|
1452
|
+
background-color: blue;
|
|
1453
|
+
color: white;
|
|
1454
|
+
border-radius: 4px;
|
|
1455
|
+
border-top-left-radius: 4px;
|
|
1456
|
+
border-top-right-radius: 4px;
|
|
1457
|
+
border-bottom-right-radius: 4px;
|
|
1458
|
+
border-bottom-left-radius: 4px;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
Inherited from ancestor[1]:
|
|
1462
|
+
.container {
|
|
1463
|
+
padding: 20px;
|
|
1464
|
+
margin: 10px;
|
|
1465
|
+
padding-top: 20px;
|
|
1466
|
+
padding-right: 20px;
|
|
1467
|
+
padding-bottom: 20px;
|
|
1468
|
+
padding-left: 20px;
|
|
1469
|
+
margin-top: 10px;
|
|
1470
|
+
margin-right: 10px;
|
|
1471
|
+
margin-bottom: 10px;
|
|
1472
|
+
margin-left: 10px;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
Inherited from ancestor[2]:
|
|
1476
|
+
body {
|
|
1477
|
+
font-family: Arial, sans-serif;
|
|
1478
|
+
color: rgb(51, 51, 51);
|
|
1479
|
+
}"
|
|
1480
|
+
`);
|
|
1481
|
+
await page.close();
|
|
1482
|
+
}, 60000);
|
|
1286
1483
|
it('should return correct layout metrics via CDP', async () => {
|
|
1287
1484
|
const browserContext = getBrowserContext();
|
|
1288
1485
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
@@ -1292,11 +1489,11 @@ describe('MCP Server Tests', () => {
|
|
|
1292
1489
|
await serviceWorker.evaluate(async () => {
|
|
1293
1490
|
await globalThis.toggleExtensionForActiveTab();
|
|
1294
1491
|
});
|
|
1295
|
-
await new Promise(r => setTimeout(r,
|
|
1296
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1492
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1493
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1297
1494
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1298
1495
|
expect(cdpPage).toBeDefined();
|
|
1299
|
-
const wsUrl = getCdpUrl();
|
|
1496
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1300
1497
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1301
1498
|
const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics');
|
|
1302
1499
|
const normalized = {
|
|
@@ -1348,7 +1545,7 @@ describe('MCP Server Tests', () => {
|
|
|
1348
1545
|
const windowDpr = await cdpPage.evaluate(() => globalThis.devicePixelRatio);
|
|
1349
1546
|
console.log('window.devicePixelRatio:', windowDpr);
|
|
1350
1547
|
expect(windowDpr).toBe(1);
|
|
1351
|
-
cdpSession.
|
|
1548
|
+
cdpSession.close();
|
|
1352
1549
|
await browser.close();
|
|
1353
1550
|
await page.close();
|
|
1354
1551
|
}, 60000);
|
|
@@ -1361,16 +1558,16 @@ describe('MCP Server Tests', () => {
|
|
|
1361
1558
|
await serviceWorker.evaluate(async () => {
|
|
1362
1559
|
await globalThis.toggleExtensionForActiveTab();
|
|
1363
1560
|
});
|
|
1364
|
-
await new Promise(r => setTimeout(r,
|
|
1365
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1561
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1562
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1366
1563
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1367
1564
|
expect(cdpPage).toBeDefined();
|
|
1368
|
-
const wsUrl = getCdpUrl();
|
|
1565
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1369
1566
|
const client = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1370
1567
|
const layoutMetrics = await client.send('Page.getLayoutMetrics');
|
|
1371
1568
|
expect(layoutMetrics.cssVisualViewport).toBeDefined();
|
|
1372
1569
|
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0);
|
|
1373
|
-
client.
|
|
1570
|
+
client.close();
|
|
1374
1571
|
await browser.close();
|
|
1375
1572
|
await page.close();
|
|
1376
1573
|
}, 60000);
|
|
@@ -1380,30 +1577,30 @@ describe('MCP Server Tests', () => {
|
|
|
1380
1577
|
await serviceWorker.evaluate(async () => {
|
|
1381
1578
|
await globalThis.disconnectEverything();
|
|
1382
1579
|
});
|
|
1383
|
-
await new Promise(r => setTimeout(r,
|
|
1580
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1384
1581
|
const targetUrl = 'https://example.com/';
|
|
1385
1582
|
const enableResult = await serviceWorker.evaluate(async (url) => {
|
|
1386
1583
|
const tab = await chrome.tabs.create({ url, active: true });
|
|
1387
|
-
await new Promise(r => setTimeout(r,
|
|
1584
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1388
1585
|
return await globalThis.toggleExtensionForActiveTab();
|
|
1389
1586
|
}, targetUrl);
|
|
1390
1587
|
console.log('Extension enabled:', enableResult);
|
|
1391
1588
|
expect(enableResult.isConnected).toBe(true);
|
|
1392
|
-
await new Promise(r => setTimeout(r,
|
|
1589
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1393
1590
|
const { Stagehand } = await import('@browserbasehq/stagehand');
|
|
1394
1591
|
const stagehand = new Stagehand({
|
|
1395
1592
|
env: 'LOCAL',
|
|
1396
1593
|
verbose: 1,
|
|
1397
1594
|
disablePino: true,
|
|
1398
1595
|
localBrowserLaunchOptions: {
|
|
1399
|
-
cdpUrl: getCdpUrl(),
|
|
1596
|
+
cdpUrl: getCdpUrl({ port: TEST_PORT }),
|
|
1400
1597
|
},
|
|
1401
1598
|
});
|
|
1402
1599
|
console.log('Initializing Stagehand...');
|
|
1403
1600
|
await stagehand.init();
|
|
1404
1601
|
console.log('Stagehand initialized');
|
|
1405
1602
|
const context = stagehand.context;
|
|
1406
|
-
console.log('Stagehand context:', context)
|
|
1603
|
+
// console.log('Stagehand context:', context)
|
|
1407
1604
|
expect(context).toBeDefined();
|
|
1408
1605
|
const pages = context.pages();
|
|
1409
1606
|
console.log('Stagehand pages:', pages.length, pages.map(p => p.url()));
|
|
@@ -1427,7 +1624,7 @@ describe('MCP Server Tests', () => {
|
|
|
1427
1624
|
await serviceWorker.evaluate(async () => {
|
|
1428
1625
|
await globalThis.toggleExtensionForActiveTab();
|
|
1429
1626
|
});
|
|
1430
|
-
await new Promise(r => setTimeout(r,
|
|
1627
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1431
1628
|
const result = await client.callTool({
|
|
1432
1629
|
name: 'execute',
|
|
1433
1630
|
arguments: {
|
|
@@ -1472,6 +1669,11 @@ describe('CDP Session Tests', () => {
|
|
|
1472
1669
|
let testCtx = null;
|
|
1473
1670
|
beforeAll(async () => {
|
|
1474
1671
|
testCtx = await setupTestContext({ tempDirPrefix: 'pw-cdp-test-' });
|
|
1672
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext);
|
|
1673
|
+
await serviceWorker.evaluate(async () => {
|
|
1674
|
+
await globalThis.disconnectEverything();
|
|
1675
|
+
});
|
|
1676
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1475
1677
|
}, 600000);
|
|
1476
1678
|
afterAll(async () => {
|
|
1477
1679
|
await cleanupTestContext(testCtx);
|
|
@@ -1482,7 +1684,7 @@ describe('CDP Session Tests', () => {
|
|
|
1482
1684
|
throw new Error('Browser not initialized');
|
|
1483
1685
|
return testCtx.browserContext;
|
|
1484
1686
|
};
|
|
1485
|
-
it('should
|
|
1687
|
+
it('should use Debugger class to set breakpoints and inspect variables', async () => {
|
|
1486
1688
|
const browserContext = getBrowserContext();
|
|
1487
1689
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1488
1690
|
const page = await browserContext.newPage();
|
|
@@ -1491,112 +1693,192 @@ describe('CDP Session Tests', () => {
|
|
|
1491
1693
|
await serviceWorker.evaluate(async () => {
|
|
1492
1694
|
await globalThis.toggleExtensionForActiveTab();
|
|
1493
1695
|
});
|
|
1494
|
-
await new Promise(r => setTimeout(r,
|
|
1495
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1696
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1697
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1496
1698
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1497
1699
|
expect(cdpPage).toBeDefined();
|
|
1498
|
-
const wsUrl = getCdpUrl();
|
|
1700
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1499
1701
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1500
|
-
|
|
1702
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
1703
|
+
await dbg.enable();
|
|
1704
|
+
expect(dbg.isPaused()).toBe(false);
|
|
1501
1705
|
const pausedPromise = new Promise((resolve) => {
|
|
1502
|
-
cdpSession.on('Debugger.paused', (
|
|
1503
|
-
resolve(
|
|
1706
|
+
cdpSession.on('Debugger.paused', () => {
|
|
1707
|
+
resolve();
|
|
1504
1708
|
});
|
|
1505
1709
|
});
|
|
1506
1710
|
cdpPage.evaluate(`
|
|
1507
1711
|
(function testFunction() {
|
|
1508
1712
|
const localVar = 'hello';
|
|
1509
1713
|
const numberVar = 42;
|
|
1510
|
-
const objVar = { key: 'value', nested: { a: 1 } };
|
|
1511
1714
|
debugger;
|
|
1512
1715
|
return localVar + numberVar;
|
|
1513
1716
|
})()
|
|
1514
1717
|
`);
|
|
1515
|
-
|
|
1718
|
+
await Promise.race([
|
|
1516
1719
|
pausedPromise,
|
|
1517
1720
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
|
|
1518
1721
|
]);
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
expect(
|
|
1525
|
-
reason: pausedEvent.reason,
|
|
1526
|
-
stackTrace: stackTrace.slice(0, 3),
|
|
1527
|
-
}).toMatchInlineSnapshot(`
|
|
1722
|
+
expect(dbg.isPaused()).toBe(true);
|
|
1723
|
+
const location = await dbg.getLocation();
|
|
1724
|
+
expect(location.callstack[0].functionName).toBe('testFunction');
|
|
1725
|
+
expect(location.sourceContext).toContain('debugger');
|
|
1726
|
+
const vars = await dbg.inspectLocalVariables();
|
|
1727
|
+
expect(vars).toMatchInlineSnapshot(`
|
|
1528
1728
|
{
|
|
1529
|
-
"
|
|
1530
|
-
"
|
|
1531
|
-
{
|
|
1532
|
-
"columnNumber": 16,
|
|
1533
|
-
"functionName": "testFunction",
|
|
1534
|
-
"lineNumber": 4,
|
|
1535
|
-
},
|
|
1536
|
-
{
|
|
1537
|
-
"columnNumber": 14,
|
|
1538
|
-
"functionName": "(anonymous)",
|
|
1539
|
-
"lineNumber": 6,
|
|
1540
|
-
},
|
|
1541
|
-
{
|
|
1542
|
-
"columnNumber": 29,
|
|
1543
|
-
"functionName": "evaluate",
|
|
1544
|
-
"lineNumber": 289,
|
|
1545
|
-
},
|
|
1546
|
-
],
|
|
1729
|
+
"localVar": "hello",
|
|
1730
|
+
"numberVar": 42,
|
|
1547
1731
|
}
|
|
1548
1732
|
`);
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1733
|
+
const evalResult = await dbg.evaluate({ expression: 'localVar + " world"' });
|
|
1734
|
+
expect(evalResult.value).toBe('hello world');
|
|
1735
|
+
await dbg.resume();
|
|
1736
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1737
|
+
expect(dbg.isPaused()).toBe(false);
|
|
1738
|
+
cdpSession.close();
|
|
1739
|
+
await browser.close();
|
|
1740
|
+
await page.close();
|
|
1741
|
+
}, 60000);
|
|
1742
|
+
it('should list scripts with Debugger class', async () => {
|
|
1743
|
+
const browserContext = getBrowserContext();
|
|
1744
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1745
|
+
const page = await browserContext.newPage();
|
|
1746
|
+
await page.setContent(`
|
|
1747
|
+
<html>
|
|
1748
|
+
<head>
|
|
1749
|
+
<script src="data:text/javascript,function testScript() { return 42; }"></script>
|
|
1750
|
+
</head>
|
|
1751
|
+
<body><h1>Script Test</h1></body>
|
|
1752
|
+
</html>
|
|
1753
|
+
`);
|
|
1754
|
+
await page.bringToFront();
|
|
1755
|
+
await serviceWorker.evaluate(async () => {
|
|
1756
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1757
|
+
});
|
|
1758
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1759
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1760
|
+
let cdpPage;
|
|
1761
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
1762
|
+
const html = await p.content();
|
|
1763
|
+
if (html.includes('Script Test')) {
|
|
1764
|
+
cdpPage = p;
|
|
1765
|
+
break;
|
|
1564
1766
|
}
|
|
1565
1767
|
}
|
|
1566
|
-
expect(
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
})
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1768
|
+
expect(cdpPage).toBeDefined();
|
|
1769
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1770
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1771
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
1772
|
+
const scripts = await dbg.listScripts();
|
|
1773
|
+
expect(scripts.length).toBeGreaterThan(0);
|
|
1774
|
+
expect(scripts[0]).toHaveProperty('scriptId');
|
|
1775
|
+
expect(scripts[0]).toHaveProperty('url');
|
|
1776
|
+
const dataScripts = await dbg.listScripts({ search: 'data:' });
|
|
1777
|
+
expect(dataScripts.length).toBeGreaterThan(0);
|
|
1778
|
+
cdpSession.close();
|
|
1779
|
+
await browser.close();
|
|
1780
|
+
await page.close();
|
|
1781
|
+
}, 60000);
|
|
1782
|
+
it('should manage breakpoints with Debugger class', async () => {
|
|
1783
|
+
const browserContext = getBrowserContext();
|
|
1784
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1785
|
+
const page = await browserContext.newPage();
|
|
1786
|
+
await page.setContent(`
|
|
1787
|
+
<html>
|
|
1788
|
+
<head>
|
|
1789
|
+
<script src="data:text/javascript,function testFunc() { return 42; }"></script>
|
|
1790
|
+
</head>
|
|
1791
|
+
<body></body>
|
|
1792
|
+
</html>
|
|
1581
1793
|
`);
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
});
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1794
|
+
await page.bringToFront();
|
|
1795
|
+
await serviceWorker.evaluate(async () => {
|
|
1796
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1797
|
+
});
|
|
1798
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1799
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1800
|
+
let cdpPage;
|
|
1801
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
1802
|
+
const html = await p.content();
|
|
1803
|
+
if (html.includes('testFunc')) {
|
|
1804
|
+
cdpPage = p;
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
expect(cdpPage).toBeDefined();
|
|
1809
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1810
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1811
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
1812
|
+
await dbg.enable();
|
|
1813
|
+
expect(dbg.listBreakpoints()).toHaveLength(0);
|
|
1814
|
+
const bpId = await dbg.setBreakpoint({ file: 'https://example.com/test.js', line: 1 });
|
|
1815
|
+
expect(typeof bpId).toBe('string');
|
|
1816
|
+
expect(dbg.listBreakpoints()).toHaveLength(1);
|
|
1817
|
+
expect(dbg.listBreakpoints()[0]).toMatchObject({
|
|
1818
|
+
id: bpId,
|
|
1819
|
+
file: 'https://example.com/test.js',
|
|
1820
|
+
line: 1,
|
|
1821
|
+
});
|
|
1822
|
+
await dbg.deleteBreakpoint({ breakpointId: bpId });
|
|
1823
|
+
expect(dbg.listBreakpoints()).toHaveLength(0);
|
|
1824
|
+
cdpSession.close();
|
|
1825
|
+
await browser.close();
|
|
1826
|
+
await page.close();
|
|
1827
|
+
}, 60000);
|
|
1828
|
+
it('should step through code with Debugger class', async () => {
|
|
1829
|
+
const browserContext = getBrowserContext();
|
|
1830
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1831
|
+
const page = await browserContext.newPage();
|
|
1832
|
+
await page.goto('https://example.com/');
|
|
1833
|
+
await page.bringToFront();
|
|
1834
|
+
await serviceWorker.evaluate(async () => {
|
|
1835
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1836
|
+
});
|
|
1837
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1838
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1839
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1840
|
+
expect(cdpPage).toBeDefined();
|
|
1841
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1842
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1843
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
1844
|
+
await dbg.enable();
|
|
1845
|
+
const pausedPromise = new Promise((resolve) => {
|
|
1846
|
+
cdpSession.on('Debugger.paused', () => resolve());
|
|
1847
|
+
});
|
|
1848
|
+
cdpPage.evaluate(`
|
|
1849
|
+
(function outer() {
|
|
1850
|
+
function inner() {
|
|
1851
|
+
const x = 1;
|
|
1852
|
+
debugger;
|
|
1853
|
+
const y = 2;
|
|
1854
|
+
return x + y;
|
|
1855
|
+
}
|
|
1856
|
+
const result = inner();
|
|
1857
|
+
return result;
|
|
1858
|
+
})()
|
|
1596
1859
|
`);
|
|
1597
|
-
await
|
|
1598
|
-
|
|
1599
|
-
|
|
1860
|
+
await pausedPromise;
|
|
1861
|
+
expect(dbg.isPaused()).toBe(true);
|
|
1862
|
+
const location1 = await dbg.getLocation();
|
|
1863
|
+
expect(location1.callstack.length).toBeGreaterThanOrEqual(2);
|
|
1864
|
+
expect(location1.callstack[0].functionName).toBe('inner');
|
|
1865
|
+
expect(location1.callstack[1].functionName).toBe('outer');
|
|
1866
|
+
const stepOverPromise = new Promise((resolve) => {
|
|
1867
|
+
cdpSession.on('Debugger.paused', () => resolve());
|
|
1868
|
+
});
|
|
1869
|
+
await dbg.stepOver();
|
|
1870
|
+
await stepOverPromise;
|
|
1871
|
+
const location2 = await dbg.getLocation();
|
|
1872
|
+
expect(location2.lineNumber).toBeGreaterThan(location1.lineNumber);
|
|
1873
|
+
const stepOutPromise = new Promise((resolve) => {
|
|
1874
|
+
cdpSession.on('Debugger.paused', () => resolve());
|
|
1875
|
+
});
|
|
1876
|
+
await dbg.stepOut();
|
|
1877
|
+
await stepOutPromise;
|
|
1878
|
+
const location3 = await dbg.getLocation();
|
|
1879
|
+
expect(location3.callstack[0].functionName).toBe('outer');
|
|
1880
|
+
await dbg.resume();
|
|
1881
|
+
cdpSession.close();
|
|
1600
1882
|
await browser.close();
|
|
1601
1883
|
await page.close();
|
|
1602
1884
|
}, 60000);
|
|
@@ -1609,11 +1891,11 @@ describe('CDP Session Tests', () => {
|
|
|
1609
1891
|
await serviceWorker.evaluate(async () => {
|
|
1610
1892
|
await globalThis.toggleExtensionForActiveTab();
|
|
1611
1893
|
});
|
|
1612
|
-
await new Promise(r => setTimeout(r,
|
|
1613
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
1894
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1895
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1614
1896
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1615
1897
|
expect(cdpPage).toBeDefined();
|
|
1616
|
-
const wsUrl = getCdpUrl();
|
|
1898
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1617
1899
|
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1618
1900
|
await cdpSession.send('Profiler.enable');
|
|
1619
1901
|
await cdpSession.send('Profiler.start');
|
|
@@ -1637,27 +1919,268 @@ describe('CDP Session Tests', () => {
|
|
|
1637
1919
|
.map(n => n.callFrame.functionName)
|
|
1638
1920
|
.filter(name => name && name.length > 0)
|
|
1639
1921
|
.slice(0, 10);
|
|
1640
|
-
expect(
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1922
|
+
expect(profile.nodes.length).toBeGreaterThan(0);
|
|
1923
|
+
expect(profile.endTime - profile.startTime).toBeGreaterThan(0);
|
|
1924
|
+
expect(functionNames.every((name) => typeof name === 'string')).toBe(true);
|
|
1925
|
+
await cdpSession.send('Profiler.disable');
|
|
1926
|
+
cdpSession.close();
|
|
1927
|
+
await browser.close();
|
|
1928
|
+
await page.close();
|
|
1929
|
+
}, 60000);
|
|
1930
|
+
it('should update Target.getTargets URL after page navigation', async () => {
|
|
1931
|
+
const browserContext = getBrowserContext();
|
|
1932
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1933
|
+
const page = await browserContext.newPage();
|
|
1934
|
+
await page.goto('https://example.com/');
|
|
1935
|
+
await page.bringToFront();
|
|
1936
|
+
await serviceWorker.evaluate(async () => {
|
|
1937
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1938
|
+
});
|
|
1939
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1940
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1941
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1942
|
+
expect(cdpPage).toBeDefined();
|
|
1943
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1944
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1945
|
+
const initialTargets = await cdpSession.send('Target.getTargets');
|
|
1946
|
+
const initialPageTarget = initialTargets.targetInfos.find(t => t.type === 'page' && t.url.includes('example.com'));
|
|
1947
|
+
expect(initialPageTarget?.url).toBe('https://example.com/');
|
|
1948
|
+
await cdpPage.goto('https://example.org/', { waitUntil: 'domcontentloaded' });
|
|
1949
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1950
|
+
const afterNavTargets = await cdpSession.send('Target.getTargets');
|
|
1951
|
+
const allPageTargets = afterNavTargets.targetInfos.filter(t => t.type === 'page');
|
|
1952
|
+
const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank');
|
|
1953
|
+
expect(aboutBlankTargets).toHaveLength(0);
|
|
1954
|
+
const exampleComTargets = allPageTargets.filter(t => t.url.includes('example.com'));
|
|
1955
|
+
expect(exampleComTargets).toHaveLength(0);
|
|
1956
|
+
const exampleOrgTargets = allPageTargets.filter(t => t.url.includes('example.org'));
|
|
1957
|
+
expect(exampleOrgTargets).toHaveLength(1);
|
|
1958
|
+
cdpSession.close();
|
|
1959
|
+
await browser.close();
|
|
1960
|
+
await page.close();
|
|
1961
|
+
}, 60000);
|
|
1962
|
+
it('should return correct targets for multiple pages via Target.getTargets', async () => {
|
|
1963
|
+
const browserContext = getBrowserContext();
|
|
1964
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1965
|
+
const page1 = await browserContext.newPage();
|
|
1966
|
+
await page1.goto('https://example.com/');
|
|
1967
|
+
await page1.bringToFront();
|
|
1968
|
+
await serviceWorker.evaluate(async () => {
|
|
1969
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1970
|
+
});
|
|
1971
|
+
const page2 = await browserContext.newPage();
|
|
1972
|
+
await page2.goto('https://example.org/');
|
|
1973
|
+
await page2.bringToFront();
|
|
1974
|
+
await serviceWorker.evaluate(async () => {
|
|
1975
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1976
|
+
});
|
|
1977
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1978
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1979
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1980
|
+
expect(cdpPage).toBeDefined();
|
|
1981
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
1982
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
1983
|
+
const { targetInfos } = await cdpSession.send('Target.getTargets');
|
|
1984
|
+
const allPageTargets = targetInfos.filter(t => t.type === 'page');
|
|
1985
|
+
const aboutBlankTargets = allPageTargets.filter(t => t.url === 'about:blank');
|
|
1986
|
+
expect(aboutBlankTargets).toHaveLength(0);
|
|
1987
|
+
const pageTargets = allPageTargets
|
|
1988
|
+
.map(t => ({ type: t.type, url: t.url }))
|
|
1989
|
+
.sort((a, b) => a.url.localeCompare(b.url));
|
|
1990
|
+
expect(pageTargets).toMatchInlineSnapshot(`
|
|
1991
|
+
[
|
|
1992
|
+
{
|
|
1993
|
+
"type": "page",
|
|
1994
|
+
"url": "https://example.com/",
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
"type": "page",
|
|
1998
|
+
"url": "https://example.org/",
|
|
1999
|
+
},
|
|
2000
|
+
]
|
|
2001
|
+
`);
|
|
2002
|
+
cdpSession.close();
|
|
2003
|
+
await browser.close();
|
|
2004
|
+
await page1.close();
|
|
2005
|
+
await page2.close();
|
|
2006
|
+
}, 60000);
|
|
2007
|
+
it('should create CDP session for page after navigation', async () => {
|
|
2008
|
+
const browserContext = getBrowserContext();
|
|
2009
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2010
|
+
const page = await browserContext.newPage();
|
|
2011
|
+
await page.goto('https://example.com/');
|
|
2012
|
+
await page.bringToFront();
|
|
2013
|
+
await serviceWorker.evaluate(async () => {
|
|
2014
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2015
|
+
});
|
|
2016
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2017
|
+
await page.goto('https://example.org/', { waitUntil: 'domcontentloaded' });
|
|
2018
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2019
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2020
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.org'));
|
|
2021
|
+
expect(cdpPage).toBeDefined();
|
|
2022
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2023
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2024
|
+
const evalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2025
|
+
expression: 'document.title',
|
|
2026
|
+
returnByValue: true,
|
|
2027
|
+
});
|
|
2028
|
+
expect(evalResult.result.value).toContain('Example Domain');
|
|
2029
|
+
cdpSession.close();
|
|
2030
|
+
await browser.close();
|
|
2031
|
+
await page.close();
|
|
2032
|
+
}, 60000);
|
|
2033
|
+
it('should maintain CDP session functionality after page URL change', async () => {
|
|
2034
|
+
const browserContext = getBrowserContext();
|
|
2035
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2036
|
+
const page = await browserContext.newPage();
|
|
2037
|
+
const initialUrl = 'https://example.com/';
|
|
2038
|
+
await page.goto(initialUrl);
|
|
2039
|
+
await page.bringToFront();
|
|
2040
|
+
await serviceWorker.evaluate(async () => {
|
|
2041
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2042
|
+
});
|
|
2043
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2044
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2045
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
2046
|
+
expect(cdpPage).toBeDefined();
|
|
2047
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2048
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2049
|
+
const initialEvalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2050
|
+
expression: 'document.title',
|
|
2051
|
+
returnByValue: true,
|
|
2052
|
+
});
|
|
2053
|
+
expect(initialEvalResult.result.value).toBe('Example Domain');
|
|
2054
|
+
const newUrl = 'https://example.org/';
|
|
2055
|
+
await cdpPage.goto(newUrl, { waitUntil: 'domcontentloaded' });
|
|
2056
|
+
expect(cdpPage.url()).toBe(newUrl);
|
|
2057
|
+
const layoutMetrics = await cdpSession.send('Page.getLayoutMetrics');
|
|
2058
|
+
expect(layoutMetrics.cssVisualViewport).toBeDefined();
|
|
2059
|
+
expect(layoutMetrics.cssVisualViewport.clientWidth).toBeGreaterThan(0);
|
|
2060
|
+
const afterNavEvalResult = await cdpSession.send('Runtime.evaluate', {
|
|
2061
|
+
expression: 'document.title',
|
|
2062
|
+
returnByValue: true,
|
|
2063
|
+
});
|
|
2064
|
+
expect(afterNavEvalResult.result.value).toContain('Example Domain');
|
|
2065
|
+
const locationResult = await cdpSession.send('Runtime.evaluate', {
|
|
2066
|
+
expression: 'window.location.href',
|
|
2067
|
+
returnByValue: true,
|
|
2068
|
+
});
|
|
2069
|
+
expect(locationResult.result.value).toBe(newUrl);
|
|
2070
|
+
cdpSession.close();
|
|
2071
|
+
await browser.close();
|
|
2072
|
+
await page.close();
|
|
2073
|
+
}, 60000);
|
|
2074
|
+
it('should pause on all exceptions with setPauseOnExceptions', async () => {
|
|
2075
|
+
const browserContext = getBrowserContext();
|
|
2076
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2077
|
+
const page = await browserContext.newPage();
|
|
2078
|
+
await page.goto('https://example.com/');
|
|
2079
|
+
await page.bringToFront();
|
|
2080
|
+
await serviceWorker.evaluate(async () => {
|
|
2081
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2082
|
+
});
|
|
2083
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2084
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2085
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
2086
|
+
expect(cdpPage).toBeDefined();
|
|
2087
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2088
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2089
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
2090
|
+
await dbg.enable();
|
|
2091
|
+
await dbg.setPauseOnExceptions({ state: 'all' });
|
|
2092
|
+
const pausedPromise = new Promise((resolve) => {
|
|
2093
|
+
cdpSession.on('Debugger.paused', () => resolve());
|
|
2094
|
+
});
|
|
2095
|
+
cdpPage.evaluate(`
|
|
2096
|
+
(function() {
|
|
2097
|
+
try {
|
|
2098
|
+
throw new Error('Caught test error');
|
|
2099
|
+
} catch (e) {
|
|
2100
|
+
// caught but should still pause with state 'all'
|
|
2101
|
+
}
|
|
2102
|
+
})()
|
|
2103
|
+
`).catch(() => { });
|
|
2104
|
+
await Promise.race([
|
|
2105
|
+
pausedPromise,
|
|
2106
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Debugger.paused timeout')), 5000))
|
|
2107
|
+
]);
|
|
2108
|
+
expect(dbg.isPaused()).toBe(true);
|
|
2109
|
+
const location = await dbg.getLocation();
|
|
2110
|
+
expect(location.sourceContext).toContain('throw');
|
|
2111
|
+
await dbg.resume();
|
|
2112
|
+
await dbg.setPauseOnExceptions({ state: 'none' });
|
|
2113
|
+
cdpSession.close();
|
|
2114
|
+
await browser.close();
|
|
2115
|
+
await page.close();
|
|
2116
|
+
}, 60000);
|
|
2117
|
+
it('should inspect local and global variables with inline snapshots', async () => {
|
|
2118
|
+
const browserContext = getBrowserContext();
|
|
2119
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2120
|
+
const page = await browserContext.newPage();
|
|
2121
|
+
await page.setContent(`
|
|
2122
|
+
<html>
|
|
2123
|
+
<head>
|
|
2124
|
+
<script>
|
|
2125
|
+
const GLOBAL_CONFIG = 'production';
|
|
2126
|
+
function runTest() {
|
|
2127
|
+
const userName = 'Alice';
|
|
2128
|
+
const userAge = 25;
|
|
2129
|
+
const settings = { theme: 'dark', lang: 'en' };
|
|
2130
|
+
const scores = [10, 20, 30];
|
|
2131
|
+
debugger;
|
|
2132
|
+
return userName;
|
|
2133
|
+
}
|
|
2134
|
+
</script>
|
|
2135
|
+
</head>
|
|
2136
|
+
<body>
|
|
2137
|
+
<button onclick="runTest()">Run</button>
|
|
2138
|
+
</body>
|
|
2139
|
+
</html>
|
|
2140
|
+
`);
|
|
2141
|
+
await page.bringToFront();
|
|
2142
|
+
await serviceWorker.evaluate(async () => {
|
|
2143
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2144
|
+
});
|
|
2145
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2146
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2147
|
+
let cdpPage;
|
|
2148
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
2149
|
+
const html = await p.content();
|
|
2150
|
+
if (html.includes('runTest')) {
|
|
2151
|
+
cdpPage = p;
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
expect(cdpPage).toBeDefined();
|
|
2156
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2157
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2158
|
+
const dbg = new Debugger({ cdp: cdpSession });
|
|
2159
|
+
await dbg.enable();
|
|
2160
|
+
const globalVars = await dbg.inspectGlobalVariables();
|
|
2161
|
+
expect(globalVars).toMatchInlineSnapshot(`
|
|
2162
|
+
[
|
|
2163
|
+
"GLOBAL_CONFIG",
|
|
2164
|
+
]
|
|
2165
|
+
`);
|
|
2166
|
+
const pausedPromise = new Promise((resolve) => {
|
|
2167
|
+
cdpSession.on('Debugger.paused', () => resolve());
|
|
2168
|
+
});
|
|
2169
|
+
cdpPage.evaluate('runTest()');
|
|
2170
|
+
await pausedPromise;
|
|
2171
|
+
expect(dbg.isPaused()).toBe(true);
|
|
2172
|
+
const localVars = await dbg.inspectLocalVariables();
|
|
2173
|
+
expect(localVars).toMatchInlineSnapshot(`
|
|
1646
2174
|
{
|
|
1647
|
-
"
|
|
1648
|
-
"
|
|
1649
|
-
"
|
|
1650
|
-
"
|
|
1651
|
-
|
|
1652
|
-
"(program)",
|
|
1653
|
-
"(idle)",
|
|
1654
|
-
"evaluate",
|
|
1655
|
-
"querySelectorAll",
|
|
1656
|
-
],
|
|
2175
|
+
"GLOBAL_CONFIG": "production",
|
|
2176
|
+
"scores": "[array]",
|
|
2177
|
+
"settings": "[object]",
|
|
2178
|
+
"userAge": 25,
|
|
2179
|
+
"userName": "Alice",
|
|
1657
2180
|
}
|
|
1658
2181
|
`);
|
|
1659
|
-
await
|
|
1660
|
-
cdpSession.
|
|
2182
|
+
await dbg.resume();
|
|
2183
|
+
cdpSession.close();
|
|
1661
2184
|
await browser.close();
|
|
1662
2185
|
await page.close();
|
|
1663
2186
|
}, 60000);
|
|
@@ -1670,8 +2193,8 @@ describe('CDP Session Tests', () => {
|
|
|
1670
2193
|
await serviceWorker.evaluate(async () => {
|
|
1671
2194
|
await globalThis.toggleExtensionForActiveTab();
|
|
1672
2195
|
});
|
|
1673
|
-
await new Promise(r => setTimeout(r,
|
|
1674
|
-
const browser = await chromium.connectOverCDP(getCdpUrl());
|
|
2196
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2197
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1675
2198
|
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
1676
2199
|
expect(cdpPage).toBeDefined();
|
|
1677
2200
|
const h1Bounds = await cdpPage.locator('h1').boundingBox();
|
|
@@ -1692,5 +2215,186 @@ describe('CDP Session Tests', () => {
|
|
|
1692
2215
|
await browser.close();
|
|
1693
2216
|
await page.close();
|
|
1694
2217
|
}, 60000);
|
|
2218
|
+
it('should use Editor class to list, read, and edit scripts', async () => {
|
|
2219
|
+
const browserContext = getBrowserContext();
|
|
2220
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2221
|
+
const page = await browserContext.newPage();
|
|
2222
|
+
await page.goto('https://example.com/');
|
|
2223
|
+
await page.bringToFront();
|
|
2224
|
+
await serviceWorker.evaluate(async () => {
|
|
2225
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2226
|
+
});
|
|
2227
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2228
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2229
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
2230
|
+
expect(cdpPage).toBeDefined();
|
|
2231
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2232
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2233
|
+
const editor = new Editor({ cdp: cdpSession });
|
|
2234
|
+
await editor.enable();
|
|
2235
|
+
await cdpPage.addScriptTag({
|
|
2236
|
+
content: `
|
|
2237
|
+
function greetUser(name) {
|
|
2238
|
+
console.log('Hello, ' + name);
|
|
2239
|
+
return 'Hello, ' + name;
|
|
2240
|
+
}
|
|
2241
|
+
`,
|
|
2242
|
+
});
|
|
2243
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2244
|
+
const scripts = await editor.list();
|
|
2245
|
+
expect(scripts.length).toBeGreaterThan(0);
|
|
2246
|
+
const matches = await editor.grep({ regex: /greetUser/ });
|
|
2247
|
+
expect(matches.length).toBeGreaterThan(0);
|
|
2248
|
+
const match = matches[0];
|
|
2249
|
+
const { content, totalLines } = await editor.read({ url: match.url });
|
|
2250
|
+
expect(content).toContain('greetUser');
|
|
2251
|
+
expect(totalLines).toBeGreaterThan(0);
|
|
2252
|
+
await editor.edit({
|
|
2253
|
+
url: match.url,
|
|
2254
|
+
oldString: "console.log('Hello, ' + name);",
|
|
2255
|
+
newString: "console.log('Hello, ' + name); console.log('EDITOR_TEST_MARKER');",
|
|
2256
|
+
});
|
|
2257
|
+
const consoleLogs = [];
|
|
2258
|
+
cdpPage.on('console', msg => {
|
|
2259
|
+
consoleLogs.push(msg.text());
|
|
2260
|
+
});
|
|
2261
|
+
await cdpPage.evaluate(() => {
|
|
2262
|
+
window.greetUser('World');
|
|
2263
|
+
});
|
|
2264
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2265
|
+
expect(consoleLogs).toContain('Hello, World');
|
|
2266
|
+
expect(consoleLogs).toContain('EDITOR_TEST_MARKER');
|
|
2267
|
+
cdpSession.close();
|
|
2268
|
+
await browser.close();
|
|
2269
|
+
await page.close();
|
|
2270
|
+
}, 60000);
|
|
2271
|
+
it('editor can list, read, and edit CSS stylesheets', async () => {
|
|
2272
|
+
const browserContext = getBrowserContext();
|
|
2273
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2274
|
+
const page = await browserContext.newPage();
|
|
2275
|
+
await page.goto('https://example.com/');
|
|
2276
|
+
await page.bringToFront();
|
|
2277
|
+
await serviceWorker.evaluate(async () => {
|
|
2278
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2279
|
+
});
|
|
2280
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2281
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2282
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com'));
|
|
2283
|
+
expect(cdpPage).toBeDefined();
|
|
2284
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2285
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2286
|
+
const editor = new Editor({ cdp: cdpSession });
|
|
2287
|
+
await editor.enable();
|
|
2288
|
+
await cdpPage.addStyleTag({
|
|
2289
|
+
content: `
|
|
2290
|
+
.editor-test-element {
|
|
2291
|
+
color: rgb(255, 0, 0);
|
|
2292
|
+
background-color: rgb(0, 0, 255);
|
|
2293
|
+
}
|
|
2294
|
+
`,
|
|
2295
|
+
});
|
|
2296
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2297
|
+
const stylesheets = await editor.list({ pattern: /inline-css:/ });
|
|
2298
|
+
expect(stylesheets.length).toBeGreaterThan(0);
|
|
2299
|
+
const cssMatches = await editor.grep({ regex: /editor-test-element/, pattern: /inline-css:/ });
|
|
2300
|
+
expect(cssMatches.length).toBeGreaterThan(0);
|
|
2301
|
+
const cssMatch = cssMatches[0];
|
|
2302
|
+
const { content, totalLines } = await editor.read({ url: cssMatch.url });
|
|
2303
|
+
expect(content).toContain('editor-test-element');
|
|
2304
|
+
expect(content).toContain('rgb(255, 0, 0)');
|
|
2305
|
+
expect(totalLines).toBeGreaterThan(0);
|
|
2306
|
+
await cdpPage.evaluate(() => {
|
|
2307
|
+
const el = document.createElement('div');
|
|
2308
|
+
el.className = 'editor-test-element';
|
|
2309
|
+
el.id = 'test-div';
|
|
2310
|
+
el.textContent = 'Test';
|
|
2311
|
+
document.body.appendChild(el);
|
|
2312
|
+
});
|
|
2313
|
+
const colorBefore = await cdpPage.evaluate(() => {
|
|
2314
|
+
const el = document.getElementById('test-div');
|
|
2315
|
+
return window.getComputedStyle(el).color;
|
|
2316
|
+
});
|
|
2317
|
+
expect(colorBefore).toBe('rgb(255, 0, 0)');
|
|
2318
|
+
await editor.edit({
|
|
2319
|
+
url: cssMatch.url,
|
|
2320
|
+
oldString: 'color: rgb(255, 0, 0);',
|
|
2321
|
+
newString: 'color: rgb(0, 255, 0);',
|
|
2322
|
+
});
|
|
2323
|
+
const colorAfter = await cdpPage.evaluate(() => {
|
|
2324
|
+
const el = document.getElementById('test-div');
|
|
2325
|
+
return window.getComputedStyle(el).color;
|
|
2326
|
+
});
|
|
2327
|
+
expect(colorAfter).toBe('rgb(0, 255, 0)');
|
|
2328
|
+
cdpSession.close();
|
|
2329
|
+
await browser.close();
|
|
2330
|
+
await page.close();
|
|
2331
|
+
}, 60000);
|
|
2332
|
+
it('should inject bippy and find React fiber with getReactSource', async () => {
|
|
2333
|
+
const browserContext = getBrowserContext();
|
|
2334
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2335
|
+
const page = await browserContext.newPage();
|
|
2336
|
+
await page.setContent(`
|
|
2337
|
+
<!DOCTYPE html>
|
|
2338
|
+
<html>
|
|
2339
|
+
<head>
|
|
2340
|
+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
|
2341
|
+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
|
2342
|
+
</head>
|
|
2343
|
+
<body>
|
|
2344
|
+
<div id="root"></div>
|
|
2345
|
+
<script>
|
|
2346
|
+
function MyComponent() {
|
|
2347
|
+
return React.createElement('button', { id: 'react-btn' }, 'Click me');
|
|
2348
|
+
}
|
|
2349
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
2350
|
+
root.render(React.createElement(MyComponent));
|
|
2351
|
+
</script>
|
|
2352
|
+
</body>
|
|
2353
|
+
</html>
|
|
2354
|
+
`);
|
|
2355
|
+
await page.bringToFront();
|
|
2356
|
+
await serviceWorker.evaluate(async () => {
|
|
2357
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
2358
|
+
});
|
|
2359
|
+
await new Promise(r => setTimeout(r, 500));
|
|
2360
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2361
|
+
const pages = browser.contexts()[0].pages();
|
|
2362
|
+
const cdpPage = pages.find(p => p.url().startsWith('about:'));
|
|
2363
|
+
expect(cdpPage).toBeDefined();
|
|
2364
|
+
const btn = cdpPage.locator('#react-btn');
|
|
2365
|
+
const btnCount = await btn.count();
|
|
2366
|
+
expect(btnCount).toBe(1);
|
|
2367
|
+
const hasBippyBefore = await cdpPage.evaluate(() => !!globalThis.__bippy);
|
|
2368
|
+
expect(hasBippyBefore).toBe(false);
|
|
2369
|
+
const wsUrl = getCdpUrl({ port: TEST_PORT });
|
|
2370
|
+
const cdpSession = await getCDPSessionForPage({ page: cdpPage, wsUrl });
|
|
2371
|
+
const { getReactSource } = await import('./react-source.js');
|
|
2372
|
+
const source = await getReactSource({ locator: btn, cdp: cdpSession });
|
|
2373
|
+
const hasBippyAfter = await cdpPage.evaluate(() => !!globalThis.__bippy);
|
|
2374
|
+
expect(hasBippyAfter).toBe(true);
|
|
2375
|
+
const hasFiber = await btn.evaluate((el) => {
|
|
2376
|
+
const bippy = globalThis.__bippy;
|
|
2377
|
+
const fiber = bippy.getFiberFromHostInstance(el);
|
|
2378
|
+
return !!fiber;
|
|
2379
|
+
});
|
|
2380
|
+
expect(hasFiber).toBe(true);
|
|
2381
|
+
const componentName = await btn.evaluate((el) => {
|
|
2382
|
+
const bippy = globalThis.__bippy;
|
|
2383
|
+
const fiber = bippy.getFiberFromHostInstance(el);
|
|
2384
|
+
let current = fiber;
|
|
2385
|
+
while (current) {
|
|
2386
|
+
if (bippy.isCompositeFiber(current)) {
|
|
2387
|
+
return bippy.getDisplayName(current.type);
|
|
2388
|
+
}
|
|
2389
|
+
current = current.return;
|
|
2390
|
+
}
|
|
2391
|
+
return null;
|
|
2392
|
+
});
|
|
2393
|
+
expect(componentName).toBe('MyComponent');
|
|
2394
|
+
console.log('Component name from fiber:', componentName);
|
|
2395
|
+
console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source);
|
|
2396
|
+
await browser.close();
|
|
2397
|
+
await page.close();
|
|
2398
|
+
}, 60000);
|
|
1695
2399
|
});
|
|
1696
2400
|
//# sourceMappingURL=mcp.test.js.map
|