playwriter 0.0.33 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria-snapshot.d.ts +68 -0
- package/dist/aria-snapshot.d.ts.map +1 -0
- package/dist/aria-snapshot.js +359 -0
- package/dist/aria-snapshot.js.map +1 -0
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +95 -5
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts +24 -3
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js +23 -0
- package/dist/cdp-session.js.map +1 -1
- package/dist/debugger-api.md +4 -3
- package/dist/debugger.d.ts +4 -3
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +3 -1
- package/dist/debugger.js.map +1 -1
- package/dist/editor-api.md +2 -2
- package/dist/editor.d.ts +2 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -0
- package/dist/editor.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +151 -14
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.test.js +340 -5
- package/dist/mcp.test.js.map +1 -1
- package/dist/protocol.d.ts +12 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/react-source.d.ts +3 -3
- package/dist/react-source.d.ts.map +1 -1
- package/dist/react-source.js +3 -1
- package/dist/react-source.js.map +1 -1
- package/dist/scoped-fs.d.ts +94 -0
- package/dist/scoped-fs.d.ts.map +1 -0
- package/dist/scoped-fs.js +356 -0
- package/dist/scoped-fs.js.map +1 -0
- package/dist/styles-api.md +3 -3
- package/dist/styles.d.ts +3 -3
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +3 -1
- package/dist/styles.js.map +1 -1
- package/package.json +13 -13
- package/src/aria-snapshot.ts +446 -0
- package/src/assets/aria-labels-github-snapshot.txt +605 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-google-snapshot.txt +110 -0
- package/src/assets/aria-labels-google.png +0 -0
- package/src/assets/aria-labels-hacker-news-snapshot.txt +1023 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/cdp-relay.ts +103 -5
- package/src/cdp-session.ts +50 -3
- package/src/debugger.ts +6 -4
- package/src/editor.ts +4 -3
- package/src/index.ts +8 -0
- package/src/mcp.test.ts +424 -5
- package/src/mcp.ts +242 -66
- package/src/prompt.md +209 -167
- package/src/protocol.ts +14 -1
- package/src/react-source.ts +5 -3
- package/src/scoped-fs.ts +411 -0
- package/src/styles.ts +5 -3
package/dist/mcp.test.js
CHANGED
|
@@ -167,8 +167,8 @@ describe('MCP Server Tests', () => {
|
|
|
167
167
|
|
|
168
168
|
Return value:
|
|
169
169
|
{
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
"url": "https://example.com/",
|
|
171
|
+
"title": "Example Domain"
|
|
172
172
|
}",
|
|
173
173
|
"type": "text",
|
|
174
174
|
},
|
|
@@ -557,7 +557,7 @@ describe('MCP Server Tests', () => {
|
|
|
557
557
|
expect(cdpPage?.url()).toBe(targetUrl);
|
|
558
558
|
await browser.close();
|
|
559
559
|
await page.close();
|
|
560
|
-
});
|
|
560
|
+
}, 60000);
|
|
561
561
|
it('should be able to reconnect after disconnecting everything', async () => {
|
|
562
562
|
const browserContext = getBrowserContext();
|
|
563
563
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
@@ -676,6 +676,73 @@ describe('MCP Server Tests', () => {
|
|
|
676
676
|
// Clean up - navigate page back to about:blank to not interfere with other tests
|
|
677
677
|
await page.goto('about:blank');
|
|
678
678
|
});
|
|
679
|
+
it('should auto-reconnect MCP after extension WebSocket reconnects', async () => {
|
|
680
|
+
// This test verifies that the MCP automatically reconnects when the browser
|
|
681
|
+
// disconnects (e.g., when the extension WebSocket reconnects and the relay
|
|
682
|
+
// server closes all playwright clients). The fix adds browser.on('disconnected')
|
|
683
|
+
// handler that clears state.isConnected, so ensureConnection() creates a new connection.
|
|
684
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext);
|
|
685
|
+
// 1. Create a test page and enable extension
|
|
686
|
+
const page = await testCtx.browserContext.newPage();
|
|
687
|
+
await page.goto('https://example.com/auto-reconnect-test');
|
|
688
|
+
await page.waitForLoadState('domcontentloaded');
|
|
689
|
+
await page.bringToFront();
|
|
690
|
+
const initialEnable = await serviceWorker.evaluate(async () => {
|
|
691
|
+
return await globalThis.toggleExtensionForActiveTab();
|
|
692
|
+
});
|
|
693
|
+
expect(initialEnable.isConnected).toBe(true);
|
|
694
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
695
|
+
// 2. Verify MCP can execute commands
|
|
696
|
+
const beforeResult = await client.callTool({
|
|
697
|
+
name: 'execute',
|
|
698
|
+
arguments: {
|
|
699
|
+
code: js `
|
|
700
|
+
const pages = context.pages();
|
|
701
|
+
const testPage = pages.find(p => p.url().includes('auto-reconnect-test'));
|
|
702
|
+
return { pagesCount: pages.length, foundTestPage: !!testPage };
|
|
703
|
+
`,
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
const beforeOutput = beforeResult.content[0].text;
|
|
707
|
+
expect(beforeOutput).toContain('foundTestPage');
|
|
708
|
+
expect(beforeOutput).toContain('true');
|
|
709
|
+
// 3. Simulate extension WebSocket reconnection
|
|
710
|
+
// This causes relay server to close all playwright client WebSockets
|
|
711
|
+
await serviceWorker.evaluate(async () => {
|
|
712
|
+
await globalThis.disconnectEverything();
|
|
713
|
+
});
|
|
714
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
715
|
+
// Re-enable extension (simulates extension reconnecting)
|
|
716
|
+
await page.bringToFront();
|
|
717
|
+
const reconnectResult = await serviceWorker.evaluate(async () => {
|
|
718
|
+
return await globalThis.toggleExtensionForActiveTab();
|
|
719
|
+
});
|
|
720
|
+
expect(reconnectResult.isConnected).toBe(true);
|
|
721
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
722
|
+
// 4. Execute command WITHOUT calling resetPlaywright()
|
|
723
|
+
// The browser.on('disconnected') handler should have cleared state.isConnected,
|
|
724
|
+
// causing ensureConnection() to automatically create a new connection
|
|
725
|
+
const afterResult = await client.callTool({
|
|
726
|
+
name: 'execute',
|
|
727
|
+
arguments: {
|
|
728
|
+
code: js `
|
|
729
|
+
const pages = context.pages();
|
|
730
|
+
const testPage = pages.find(p => p.url().includes('auto-reconnect-test'));
|
|
731
|
+
return { pagesCount: pages.length, foundTestPage: !!testPage, url: testPage?.url() };
|
|
732
|
+
`,
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
const afterOutput = afterResult.content[0].text;
|
|
736
|
+
// The command should succeed and find our test page
|
|
737
|
+
expect(afterOutput).toContain('foundTestPage');
|
|
738
|
+
expect(afterOutput).toContain('true');
|
|
739
|
+
expect(afterOutput).toContain('auto-reconnect-test');
|
|
740
|
+
// Should NOT contain error about extension not connected
|
|
741
|
+
expect(afterOutput).not.toContain('Extension not connected');
|
|
742
|
+
expect(afterResult.isError).not.toBe(true);
|
|
743
|
+
// Clean up
|
|
744
|
+
await page.goto('about:blank');
|
|
745
|
+
});
|
|
679
746
|
it('should capture browser console logs with getLatestLogs', async () => {
|
|
680
747
|
// Ensure clean state and clear any existing logs
|
|
681
748
|
const resetResult = await client.callTool({
|
|
@@ -1057,6 +1124,73 @@ describe('MCP Server Tests', () => {
|
|
|
1057
1124
|
await cdpBrowser.close();
|
|
1058
1125
|
await page.close();
|
|
1059
1126
|
}, 30000);
|
|
1127
|
+
it('should be usable after toggle with valid URL', async () => {
|
|
1128
|
+
// This test validates the extension properly waits for valid URLs before
|
|
1129
|
+
// sending Target.attachedToTarget. Uses Discord - a heavy React SPA.
|
|
1130
|
+
//
|
|
1131
|
+
// We use waitForEvent('page') to wait for Playwright to process the event.
|
|
1132
|
+
// The KEY assertion is that when the event fires, the URL is VALID (not empty).
|
|
1133
|
+
// Before the fix: event fired with empty URL -> page broken forever
|
|
1134
|
+
// After the fix: event fires with valid URL -> page works immediately
|
|
1135
|
+
const _browserContext = getBrowserContext();
|
|
1136
|
+
const serviceWorker = await getExtensionServiceWorker(_browserContext);
|
|
1137
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1138
|
+
const context = browser.contexts()[0];
|
|
1139
|
+
const page = await _browserContext.newPage();
|
|
1140
|
+
await page.goto('https://discord.com/login');
|
|
1141
|
+
await page.bringToFront();
|
|
1142
|
+
// Set up listener BEFORE toggle
|
|
1143
|
+
const pagePromise = context.waitForEvent('page', { timeout: 10000 });
|
|
1144
|
+
// Toggle extension - extension waits for valid URL before sending event
|
|
1145
|
+
await serviceWorker.evaluate(async () => {
|
|
1146
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1147
|
+
});
|
|
1148
|
+
// Wait for page event
|
|
1149
|
+
const targetPage = await pagePromise;
|
|
1150
|
+
console.log('Page URL when event fired:', targetPage.url());
|
|
1151
|
+
// KEY ASSERTION: URL must NOT be empty - this is what the extension fix guarantees
|
|
1152
|
+
expect(targetPage.url()).not.toBe('');
|
|
1153
|
+
expect(targetPage.url()).not.toBe(':');
|
|
1154
|
+
expect(targetPage.url()).toContain('discord.com');
|
|
1155
|
+
// evaluate() works immediately - no waiting needed
|
|
1156
|
+
const result = await targetPage.evaluate(() => window.location.href);
|
|
1157
|
+
expect(result).toContain('discord.com');
|
|
1158
|
+
await browser.close();
|
|
1159
|
+
await page.close();
|
|
1160
|
+
}, 60000);
|
|
1161
|
+
it('should have non-empty URLs when connecting to already-loaded pages', async () => {
|
|
1162
|
+
// This test validates that when we connect to a browser with already-loaded pages,
|
|
1163
|
+
// all pages have non-empty URLs. Empty URLs break Playwright permanently.
|
|
1164
|
+
const _browserContext = getBrowserContext();
|
|
1165
|
+
const serviceWorker = await getExtensionServiceWorker(_browserContext);
|
|
1166
|
+
// Create and fully load a heavy page BEFORE connecting
|
|
1167
|
+
const page = await _browserContext.newPage();
|
|
1168
|
+
await page.goto('https://discord.com/login', { waitUntil: 'load' });
|
|
1169
|
+
await page.bringToFront();
|
|
1170
|
+
// Toggle extension to attach to the loaded page
|
|
1171
|
+
await serviceWorker.evaluate(async () => {
|
|
1172
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1173
|
+
});
|
|
1174
|
+
// NOW connect via CDP - page should already be attached
|
|
1175
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1176
|
+
const context = browser.contexts()[0];
|
|
1177
|
+
// Get all pages and verify NONE have empty URLs
|
|
1178
|
+
const pages = context.pages();
|
|
1179
|
+
console.log('All page URLs:', pages.map(p => p.url()));
|
|
1180
|
+
expect(pages.length).toBeGreaterThan(0);
|
|
1181
|
+
for (const p of pages) {
|
|
1182
|
+
expect(p.url()).not.toBe('');
|
|
1183
|
+
expect(p.url()).not.toBe(':');
|
|
1184
|
+
expect(p.url()).not.toBeUndefined();
|
|
1185
|
+
}
|
|
1186
|
+
// Find Discord page and verify it works
|
|
1187
|
+
const discordPage = pages.find(p => p.url().includes('discord.com'));
|
|
1188
|
+
expect(discordPage).toBeDefined();
|
|
1189
|
+
const result = await discordPage.evaluate(() => window.location.href);
|
|
1190
|
+
expect(result).toContain('discord.com');
|
|
1191
|
+
await browser.close();
|
|
1192
|
+
await page.close();
|
|
1193
|
+
}, 60000);
|
|
1060
1194
|
it('should maintain correct page.url() with iframe-heavy pages', async () => {
|
|
1061
1195
|
const browserContext = getBrowserContext();
|
|
1062
1196
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
@@ -1647,8 +1781,8 @@ describe('MCP Server Tests', () => {
|
|
|
1647
1781
|
{
|
|
1648
1782
|
"text": "Return value:
|
|
1649
1783
|
{
|
|
1650
|
-
"
|
|
1651
|
-
"
|
|
1784
|
+
\"matchesDark\": false,
|
|
1785
|
+
\"matchesLight\": true
|
|
1652
1786
|
}",
|
|
1653
1787
|
"type": "text",
|
|
1654
1788
|
},
|
|
@@ -1656,6 +1790,154 @@ describe('MCP Server Tests', () => {
|
|
|
1656
1790
|
`);
|
|
1657
1791
|
await page.close();
|
|
1658
1792
|
}, 60000);
|
|
1793
|
+
it('should get aria ref for locator using getAriaSnapshot', async () => {
|
|
1794
|
+
const browserContext = getBrowserContext();
|
|
1795
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1796
|
+
const page = await browserContext.newPage();
|
|
1797
|
+
await page.setContent(`
|
|
1798
|
+
<html>
|
|
1799
|
+
<body>
|
|
1800
|
+
<button id="submit-btn">Submit Form</button>
|
|
1801
|
+
<a href="/about">About Us</a>
|
|
1802
|
+
<input type="text" placeholder="Enter your name" />
|
|
1803
|
+
</body>
|
|
1804
|
+
</html>
|
|
1805
|
+
`);
|
|
1806
|
+
await page.bringToFront();
|
|
1807
|
+
await serviceWorker.evaluate(async () => {
|
|
1808
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1809
|
+
});
|
|
1810
|
+
await new Promise(r => setTimeout(r, 400));
|
|
1811
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1812
|
+
let cdpPage;
|
|
1813
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
1814
|
+
const html = await p.content();
|
|
1815
|
+
if (html.includes('submit-btn')) {
|
|
1816
|
+
cdpPage = p;
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
expect(cdpPage).toBeDefined();
|
|
1821
|
+
const { getAriaSnapshot } = await import('./aria-snapshot.js');
|
|
1822
|
+
// Get aria snapshot and verify we can get refs
|
|
1823
|
+
const ariaResult = await getAriaSnapshot({ page: cdpPage });
|
|
1824
|
+
expect(ariaResult.snapshot).toBeDefined();
|
|
1825
|
+
expect(ariaResult.snapshot.length).toBeGreaterThan(0);
|
|
1826
|
+
expect(ariaResult.snapshot).toContain('Submit Form');
|
|
1827
|
+
// Verify refToElement map is populated
|
|
1828
|
+
expect(ariaResult.refToElement.size).toBeGreaterThan(0);
|
|
1829
|
+
console.log('RefToElement map size:', ariaResult.refToElement.size);
|
|
1830
|
+
console.log('RefToElement entries:', [...ariaResult.refToElement.entries()]);
|
|
1831
|
+
// Verify we can select elements using aria-ref selectors
|
|
1832
|
+
const btnViaAriaRef = cdpPage.locator('aria-ref=e2');
|
|
1833
|
+
const btnTextViaRef = await btnViaAriaRef.textContent();
|
|
1834
|
+
console.log('Button text via aria-ref=e2:', btnTextViaRef);
|
|
1835
|
+
expect(btnTextViaRef).toBe('Submit Form');
|
|
1836
|
+
// Get ref for the submit button using getRefForLocator
|
|
1837
|
+
const submitBtn = cdpPage.locator('#submit-btn');
|
|
1838
|
+
const btnAriaRef = await ariaResult.getRefForLocator(submitBtn);
|
|
1839
|
+
console.log('Button ariaRef:', btnAriaRef);
|
|
1840
|
+
expect(btnAriaRef).toBeDefined();
|
|
1841
|
+
expect(btnAriaRef?.role).toBe('button');
|
|
1842
|
+
expect(btnAriaRef?.name).toBe('Submit Form');
|
|
1843
|
+
expect(btnAriaRef?.ref).toMatch(/^e\d+$/);
|
|
1844
|
+
// Verify the ref matches what we can use to select
|
|
1845
|
+
const btnFromRef = cdpPage.locator(`aria-ref=${btnAriaRef?.ref}`);
|
|
1846
|
+
const btnText = await btnFromRef.textContent();
|
|
1847
|
+
expect(btnText).toBe('Submit Form');
|
|
1848
|
+
// Test getRefStringForLocator
|
|
1849
|
+
const btnRefStr = await ariaResult.getRefStringForLocator(submitBtn);
|
|
1850
|
+
console.log('Button ref string:', btnRefStr);
|
|
1851
|
+
expect(btnRefStr).toBe(btnAriaRef?.ref);
|
|
1852
|
+
// Test link
|
|
1853
|
+
const aboutLink = cdpPage.locator('a');
|
|
1854
|
+
const linkAriaRef = await ariaResult.getRefForLocator(aboutLink);
|
|
1855
|
+
console.log('Link ariaRef:', linkAriaRef);
|
|
1856
|
+
expect(linkAriaRef).toBeDefined();
|
|
1857
|
+
expect(linkAriaRef?.role).toBe('link');
|
|
1858
|
+
expect(linkAriaRef?.name).toBe('About Us');
|
|
1859
|
+
// Verify the link ref works
|
|
1860
|
+
const linkFromRef = cdpPage.locator(`aria-ref=${linkAriaRef?.ref}`);
|
|
1861
|
+
const linkText = await linkFromRef.textContent();
|
|
1862
|
+
expect(linkText).toBe('About Us');
|
|
1863
|
+
// Test input field
|
|
1864
|
+
const inputField = cdpPage.locator('input');
|
|
1865
|
+
const inputAriaRef = await ariaResult.getRefForLocator(inputField);
|
|
1866
|
+
console.log('Input ariaRef:', inputAriaRef);
|
|
1867
|
+
expect(inputAriaRef).toBeDefined();
|
|
1868
|
+
expect(inputAriaRef?.role).toBe('textbox');
|
|
1869
|
+
// Test batch getRefsForLocators - single evaluate call for multiple elements
|
|
1870
|
+
const batchRefs = await ariaResult.getRefsForLocators([submitBtn, aboutLink, inputField]);
|
|
1871
|
+
console.log('Batch refs:', batchRefs);
|
|
1872
|
+
expect(batchRefs).toHaveLength(3);
|
|
1873
|
+
expect(batchRefs[0]?.ref).toBe(btnAriaRef?.ref);
|
|
1874
|
+
expect(batchRefs[1]?.ref).toBe(linkAriaRef?.ref);
|
|
1875
|
+
expect(batchRefs[2]?.ref).toBe(inputAriaRef?.ref);
|
|
1876
|
+
await browser.close();
|
|
1877
|
+
await page.close();
|
|
1878
|
+
}, 60000);
|
|
1879
|
+
it('should show aria ref labels on real pages and save screenshots', async () => {
|
|
1880
|
+
const browserContext = getBrowserContext();
|
|
1881
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
1882
|
+
const { showAriaRefLabels, hideAriaRefLabels } = await import('./aria-snapshot.js');
|
|
1883
|
+
const fs = await import('node:fs');
|
|
1884
|
+
const path = await import('node:path');
|
|
1885
|
+
// Create assets folder for screenshots
|
|
1886
|
+
const assetsDir = path.join(path.dirname(new URL(import.meta.url).pathname), 'assets');
|
|
1887
|
+
if (!fs.existsSync(assetsDir)) {
|
|
1888
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
1889
|
+
}
|
|
1890
|
+
const testPages = [
|
|
1891
|
+
{ name: 'hacker-news', url: 'https://news.ycombinator.com/' },
|
|
1892
|
+
{ name: 'google', url: 'https://www.google.com/' },
|
|
1893
|
+
{ name: 'github', url: 'https://github.com/' },
|
|
1894
|
+
];
|
|
1895
|
+
// Create all pages and enable extension for each
|
|
1896
|
+
const pages = await Promise.all(testPages.map(async ({ name, url }) => {
|
|
1897
|
+
const page = await browserContext.newPage();
|
|
1898
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
1899
|
+
return { name, url, page };
|
|
1900
|
+
}));
|
|
1901
|
+
// Enable extension for each tab (must be done sequentially as it uses active tab)
|
|
1902
|
+
for (const { page } of pages) {
|
|
1903
|
+
await page.bringToFront();
|
|
1904
|
+
await serviceWorker.evaluate(async () => {
|
|
1905
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
// Connect CDP and process all pages concurrently
|
|
1909
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
1910
|
+
await Promise.all(pages.map(async ({ name, url, page }) => {
|
|
1911
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes(new URL(url).hostname));
|
|
1912
|
+
if (!cdpPage) {
|
|
1913
|
+
console.log(`Could not find CDP page for ${name}, skipping...`);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
// Show aria ref labels
|
|
1917
|
+
const { snapshot, labelCount } = await showAriaRefLabels({ page: cdpPage });
|
|
1918
|
+
console.log(`${name}: ${labelCount} labels shown`);
|
|
1919
|
+
expect(labelCount).toBeGreaterThan(0);
|
|
1920
|
+
// Take screenshot with labels visible
|
|
1921
|
+
const screenshot = await cdpPage.screenshot({ type: 'png', fullPage: false });
|
|
1922
|
+
const screenshotPath = path.join(assetsDir, `aria-labels-${name}.png`);
|
|
1923
|
+
fs.writeFileSync(screenshotPath, screenshot);
|
|
1924
|
+
console.log(`Screenshot saved: ${screenshotPath}`);
|
|
1925
|
+
// Save snapshot text for reference
|
|
1926
|
+
const snapshotPath = path.join(assetsDir, `aria-labels-${name}-snapshot.txt`);
|
|
1927
|
+
fs.writeFileSync(snapshotPath, snapshot);
|
|
1928
|
+
// Verify labels are in DOM
|
|
1929
|
+
const labelElements = await cdpPage.evaluate(() => document.querySelectorAll('.__pw_label__').length);
|
|
1930
|
+
expect(labelElements).toBe(labelCount);
|
|
1931
|
+
// Cleanup
|
|
1932
|
+
await hideAriaRefLabels({ page: cdpPage });
|
|
1933
|
+
// Verify labels removed
|
|
1934
|
+
const labelsAfterHide = await cdpPage.evaluate(() => document.getElementById('__playwriter_labels__'));
|
|
1935
|
+
expect(labelsAfterHide).toBeNull();
|
|
1936
|
+
await page.close();
|
|
1937
|
+
}));
|
|
1938
|
+
await browser.close();
|
|
1939
|
+
console.log(`Screenshots saved to: ${assetsDir}`);
|
|
1940
|
+
}, 120000);
|
|
1659
1941
|
});
|
|
1660
1942
|
function tryJsonParse(str) {
|
|
1661
1943
|
try {
|
|
@@ -2397,4 +2679,57 @@ describe('CDP Session Tests', () => {
|
|
|
2397
2679
|
await page.close();
|
|
2398
2680
|
}, 60000);
|
|
2399
2681
|
});
|
|
2682
|
+
describe('Auto-enable Tests', () => {
|
|
2683
|
+
let testCtx = null;
|
|
2684
|
+
// Set env var before any setup runs
|
|
2685
|
+
process.env.PLAYWRITER_AUTO_ENABLE = '1';
|
|
2686
|
+
beforeAll(async () => {
|
|
2687
|
+
testCtx = await setupTestContext({ tempDirPrefix: 'pw-auto-test-' });
|
|
2688
|
+
// Disconnect all tabs to start with a clean state
|
|
2689
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext);
|
|
2690
|
+
await serviceWorker.evaluate(async () => {
|
|
2691
|
+
await globalThis.disconnectEverything();
|
|
2692
|
+
});
|
|
2693
|
+
await new Promise(r => setTimeout(r, 100));
|
|
2694
|
+
}, 600000);
|
|
2695
|
+
afterAll(async () => {
|
|
2696
|
+
delete process.env.PLAYWRITER_AUTO_ENABLE;
|
|
2697
|
+
await cleanupTestContext(testCtx);
|
|
2698
|
+
testCtx = null;
|
|
2699
|
+
});
|
|
2700
|
+
const getBrowserContext = () => {
|
|
2701
|
+
if (!testCtx?.browserContext)
|
|
2702
|
+
throw new Error('Browser not initialized');
|
|
2703
|
+
return testCtx.browserContext;
|
|
2704
|
+
};
|
|
2705
|
+
it('should auto-create a tab when Playwright connects and no tabs exist', async () => {
|
|
2706
|
+
const browserContext = getBrowserContext();
|
|
2707
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
2708
|
+
// Verify no tabs are connected
|
|
2709
|
+
const tabCountBefore = await serviceWorker.evaluate(() => {
|
|
2710
|
+
const state = globalThis.getExtensionState();
|
|
2711
|
+
return state.tabs.size;
|
|
2712
|
+
});
|
|
2713
|
+
expect(tabCountBefore).toBe(0);
|
|
2714
|
+
// Connect Playwright - this should trigger auto-create
|
|
2715
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }));
|
|
2716
|
+
// Verify a page was auto-created
|
|
2717
|
+
const pages = browser.contexts()[0].pages();
|
|
2718
|
+
expect(pages.length).toBeGreaterThan(0);
|
|
2719
|
+
expect(pages.length).toBe(1);
|
|
2720
|
+
const autoCreatedPage = pages[0];
|
|
2721
|
+
expect(autoCreatedPage.url()).toBe('about:blank');
|
|
2722
|
+
// Verify extension state shows the tab as connected
|
|
2723
|
+
const tabCountAfter = await serviceWorker.evaluate(() => {
|
|
2724
|
+
const state = globalThis.getExtensionState();
|
|
2725
|
+
return state.tabs.size;
|
|
2726
|
+
});
|
|
2727
|
+
expect(tabCountAfter).toBe(1);
|
|
2728
|
+
// Verify we can interact with the auto-created page
|
|
2729
|
+
await autoCreatedPage.setContent('<h1>Auto-created page</h1>');
|
|
2730
|
+
const title = await autoCreatedPage.locator('h1').textContent();
|
|
2731
|
+
expect(title).toBe('Auto-created page');
|
|
2732
|
+
await browser.close();
|
|
2733
|
+
}, 60000);
|
|
2734
|
+
});
|
|
2400
2735
|
//# sourceMappingURL=mcp.test.js.map
|