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/src/mcp.test.ts
CHANGED
|
@@ -217,8 +217,8 @@ describe('MCP Server Tests', () => {
|
|
|
217
217
|
|
|
218
218
|
Return value:
|
|
219
219
|
{
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
"url": "https://example.com/",
|
|
221
|
+
"title": "Example Domain"
|
|
222
222
|
}",
|
|
223
223
|
"type": "text",
|
|
224
224
|
},
|
|
@@ -686,7 +686,7 @@ describe('MCP Server Tests', () => {
|
|
|
686
686
|
|
|
687
687
|
await browser.close()
|
|
688
688
|
await page.close()
|
|
689
|
-
})
|
|
689
|
+
}, 60000)
|
|
690
690
|
|
|
691
691
|
it('should be able to reconnect after disconnecting everything', async () => {
|
|
692
692
|
const browserContext = getBrowserContext()
|
|
@@ -824,6 +824,83 @@ describe('MCP Server Tests', () => {
|
|
|
824
824
|
await page.goto('about:blank')
|
|
825
825
|
})
|
|
826
826
|
|
|
827
|
+
it('should auto-reconnect MCP after extension WebSocket reconnects', async () => {
|
|
828
|
+
// This test verifies that the MCP automatically reconnects when the browser
|
|
829
|
+
// disconnects (e.g., when the extension WebSocket reconnects and the relay
|
|
830
|
+
// server closes all playwright clients). The fix adds browser.on('disconnected')
|
|
831
|
+
// handler that clears state.isConnected, so ensureConnection() creates a new connection.
|
|
832
|
+
|
|
833
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx!.browserContext)
|
|
834
|
+
|
|
835
|
+
// 1. Create a test page and enable extension
|
|
836
|
+
const page = await testCtx!.browserContext.newPage()
|
|
837
|
+
await page.goto('https://example.com/auto-reconnect-test')
|
|
838
|
+
await page.waitForLoadState('domcontentloaded')
|
|
839
|
+
await page.bringToFront()
|
|
840
|
+
|
|
841
|
+
const initialEnable = await serviceWorker.evaluate(async () => {
|
|
842
|
+
return await globalThis.toggleExtensionForActiveTab()
|
|
843
|
+
})
|
|
844
|
+
expect(initialEnable.isConnected).toBe(true)
|
|
845
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
846
|
+
|
|
847
|
+
// 2. Verify MCP can execute commands
|
|
848
|
+
const beforeResult = await client.callTool({
|
|
849
|
+
name: 'execute',
|
|
850
|
+
arguments: {
|
|
851
|
+
code: js`
|
|
852
|
+
const pages = context.pages();
|
|
853
|
+
const testPage = pages.find(p => p.url().includes('auto-reconnect-test'));
|
|
854
|
+
return { pagesCount: pages.length, foundTestPage: !!testPage };
|
|
855
|
+
`,
|
|
856
|
+
},
|
|
857
|
+
})
|
|
858
|
+
const beforeOutput = (beforeResult as any).content[0].text
|
|
859
|
+
expect(beforeOutput).toContain('foundTestPage')
|
|
860
|
+
expect(beforeOutput).toContain('true')
|
|
861
|
+
|
|
862
|
+
// 3. Simulate extension WebSocket reconnection
|
|
863
|
+
// This causes relay server to close all playwright client WebSockets
|
|
864
|
+
await serviceWorker.evaluate(async () => {
|
|
865
|
+
await globalThis.disconnectEverything()
|
|
866
|
+
})
|
|
867
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
868
|
+
|
|
869
|
+
// Re-enable extension (simulates extension reconnecting)
|
|
870
|
+
await page.bringToFront()
|
|
871
|
+
const reconnectResult = await serviceWorker.evaluate(async () => {
|
|
872
|
+
return await globalThis.toggleExtensionForActiveTab()
|
|
873
|
+
})
|
|
874
|
+
expect(reconnectResult.isConnected).toBe(true)
|
|
875
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
876
|
+
|
|
877
|
+
// 4. Execute command WITHOUT calling resetPlaywright()
|
|
878
|
+
// The browser.on('disconnected') handler should have cleared state.isConnected,
|
|
879
|
+
// causing ensureConnection() to automatically create a new connection
|
|
880
|
+
const afterResult = await client.callTool({
|
|
881
|
+
name: 'execute',
|
|
882
|
+
arguments: {
|
|
883
|
+
code: js`
|
|
884
|
+
const pages = context.pages();
|
|
885
|
+
const testPage = pages.find(p => p.url().includes('auto-reconnect-test'));
|
|
886
|
+
return { pagesCount: pages.length, foundTestPage: !!testPage, url: testPage?.url() };
|
|
887
|
+
`,
|
|
888
|
+
},
|
|
889
|
+
})
|
|
890
|
+
|
|
891
|
+
const afterOutput = (afterResult as any).content[0].text
|
|
892
|
+
// The command should succeed and find our test page
|
|
893
|
+
expect(afterOutput).toContain('foundTestPage')
|
|
894
|
+
expect(afterOutput).toContain('true')
|
|
895
|
+
expect(afterOutput).toContain('auto-reconnect-test')
|
|
896
|
+
// Should NOT contain error about extension not connected
|
|
897
|
+
expect(afterOutput).not.toContain('Extension not connected')
|
|
898
|
+
expect((afterResult as any).isError).not.toBe(true)
|
|
899
|
+
|
|
900
|
+
// Clean up
|
|
901
|
+
await page.goto('about:blank')
|
|
902
|
+
})
|
|
903
|
+
|
|
827
904
|
it('should capture browser console logs with getLatestLogs', async () => {
|
|
828
905
|
// Ensure clean state and clear any existing logs
|
|
829
906
|
const resetResult = await client.callTool({
|
|
@@ -1260,6 +1337,92 @@ describe('MCP Server Tests', () => {
|
|
|
1260
1337
|
await page.close()
|
|
1261
1338
|
}, 30000)
|
|
1262
1339
|
|
|
1340
|
+
it('should be usable after toggle with valid URL', async () => {
|
|
1341
|
+
// This test validates the extension properly waits for valid URLs before
|
|
1342
|
+
// sending Target.attachedToTarget. Uses Discord - a heavy React SPA.
|
|
1343
|
+
//
|
|
1344
|
+
// We use waitForEvent('page') to wait for Playwright to process the event.
|
|
1345
|
+
// The KEY assertion is that when the event fires, the URL is VALID (not empty).
|
|
1346
|
+
// Before the fix: event fired with empty URL -> page broken forever
|
|
1347
|
+
// After the fix: event fires with valid URL -> page works immediately
|
|
1348
|
+
|
|
1349
|
+
const _browserContext = getBrowserContext()
|
|
1350
|
+
const serviceWorker = await getExtensionServiceWorker(_browserContext)
|
|
1351
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1352
|
+
const context = browser.contexts()[0]
|
|
1353
|
+
|
|
1354
|
+
const page = await _browserContext.newPage()
|
|
1355
|
+
await page.goto('https://discord.com/login')
|
|
1356
|
+
await page.bringToFront()
|
|
1357
|
+
|
|
1358
|
+
// Set up listener BEFORE toggle
|
|
1359
|
+
const pagePromise = context.waitForEvent('page', { timeout: 10000 })
|
|
1360
|
+
|
|
1361
|
+
// Toggle extension - extension waits for valid URL before sending event
|
|
1362
|
+
await serviceWorker.evaluate(async () => {
|
|
1363
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
1364
|
+
})
|
|
1365
|
+
|
|
1366
|
+
// Wait for page event
|
|
1367
|
+
const targetPage = await pagePromise
|
|
1368
|
+
console.log('Page URL when event fired:', targetPage.url())
|
|
1369
|
+
|
|
1370
|
+
// KEY ASSERTION: URL must NOT be empty - this is what the extension fix guarantees
|
|
1371
|
+
expect(targetPage.url()).not.toBe('')
|
|
1372
|
+
expect(targetPage.url()).not.toBe(':')
|
|
1373
|
+
expect(targetPage.url()).toContain('discord.com')
|
|
1374
|
+
|
|
1375
|
+
// evaluate() works immediately - no waiting needed
|
|
1376
|
+
const result = await targetPage.evaluate(() => window.location.href)
|
|
1377
|
+
expect(result).toContain('discord.com')
|
|
1378
|
+
|
|
1379
|
+
await browser.close()
|
|
1380
|
+
await page.close()
|
|
1381
|
+
}, 60000)
|
|
1382
|
+
|
|
1383
|
+
it('should have non-empty URLs when connecting to already-loaded pages', async () => {
|
|
1384
|
+
// This test validates that when we connect to a browser with already-loaded pages,
|
|
1385
|
+
// all pages have non-empty URLs. Empty URLs break Playwright permanently.
|
|
1386
|
+
|
|
1387
|
+
const _browserContext = getBrowserContext()
|
|
1388
|
+
const serviceWorker = await getExtensionServiceWorker(_browserContext)
|
|
1389
|
+
|
|
1390
|
+
// Create and fully load a heavy page BEFORE connecting
|
|
1391
|
+
const page = await _browserContext.newPage()
|
|
1392
|
+
await page.goto('https://discord.com/login', { waitUntil: 'load' })
|
|
1393
|
+
await page.bringToFront()
|
|
1394
|
+
|
|
1395
|
+
// Toggle extension to attach to the loaded page
|
|
1396
|
+
await serviceWorker.evaluate(async () => {
|
|
1397
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
1398
|
+
})
|
|
1399
|
+
|
|
1400
|
+
// NOW connect via CDP - page should already be attached
|
|
1401
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
1402
|
+
const context = browser.contexts()[0]
|
|
1403
|
+
|
|
1404
|
+
// Get all pages and verify NONE have empty URLs
|
|
1405
|
+
const pages = context.pages()
|
|
1406
|
+
console.log('All page URLs:', pages.map(p => p.url()))
|
|
1407
|
+
|
|
1408
|
+
expect(pages.length).toBeGreaterThan(0)
|
|
1409
|
+
for (const p of pages) {
|
|
1410
|
+
expect(p.url()).not.toBe('')
|
|
1411
|
+
expect(p.url()).not.toBe(':')
|
|
1412
|
+
expect(p.url()).not.toBeUndefined()
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Find Discord page and verify it works
|
|
1416
|
+
const discordPage = pages.find(p => p.url().includes('discord.com'))
|
|
1417
|
+
expect(discordPage).toBeDefined()
|
|
1418
|
+
|
|
1419
|
+
const result = await discordPage!.evaluate(() => window.location.href)
|
|
1420
|
+
expect(result).toContain('discord.com')
|
|
1421
|
+
|
|
1422
|
+
await browser.close()
|
|
1423
|
+
await page.close()
|
|
1424
|
+
}, 60000)
|
|
1425
|
+
|
|
1263
1426
|
it('should maintain correct page.url() with iframe-heavy pages', async () => {
|
|
1264
1427
|
const browserContext = getBrowserContext()
|
|
1265
1428
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
@@ -1943,8 +2106,8 @@ describe('MCP Server Tests', () => {
|
|
|
1943
2106
|
{
|
|
1944
2107
|
"text": "Return value:
|
|
1945
2108
|
{
|
|
1946
|
-
"
|
|
1947
|
-
"
|
|
2109
|
+
\"matchesDark\": false,
|
|
2110
|
+
\"matchesLight\": true
|
|
1948
2111
|
}",
|
|
1949
2112
|
"type": "text",
|
|
1950
2113
|
},
|
|
@@ -1954,6 +2117,196 @@ describe('MCP Server Tests', () => {
|
|
|
1954
2117
|
await page.close()
|
|
1955
2118
|
}, 60000)
|
|
1956
2119
|
|
|
2120
|
+
it('should get aria ref for locator using getAriaSnapshot', async () => {
|
|
2121
|
+
const browserContext = getBrowserContext()
|
|
2122
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2123
|
+
|
|
2124
|
+
const page = await browserContext.newPage()
|
|
2125
|
+
await page.setContent(`
|
|
2126
|
+
<html>
|
|
2127
|
+
<body>
|
|
2128
|
+
<button id="submit-btn">Submit Form</button>
|
|
2129
|
+
<a href="/about">About Us</a>
|
|
2130
|
+
<input type="text" placeholder="Enter your name" />
|
|
2131
|
+
</body>
|
|
2132
|
+
</html>
|
|
2133
|
+
`)
|
|
2134
|
+
await page.bringToFront()
|
|
2135
|
+
|
|
2136
|
+
await serviceWorker.evaluate(async () => {
|
|
2137
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2138
|
+
})
|
|
2139
|
+
await new Promise(r => setTimeout(r, 400))
|
|
2140
|
+
|
|
2141
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2142
|
+
let cdpPage
|
|
2143
|
+
for (const p of browser.contexts()[0].pages()) {
|
|
2144
|
+
const html = await p.content()
|
|
2145
|
+
if (html.includes('submit-btn')) {
|
|
2146
|
+
cdpPage = p
|
|
2147
|
+
break
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
expect(cdpPage).toBeDefined()
|
|
2151
|
+
|
|
2152
|
+
const { getAriaSnapshot } = await import('./aria-snapshot.js')
|
|
2153
|
+
|
|
2154
|
+
// Get aria snapshot and verify we can get refs
|
|
2155
|
+
const ariaResult = await getAriaSnapshot({ page: cdpPage! })
|
|
2156
|
+
|
|
2157
|
+
expect(ariaResult.snapshot).toBeDefined()
|
|
2158
|
+
expect(ariaResult.snapshot.length).toBeGreaterThan(0)
|
|
2159
|
+
expect(ariaResult.snapshot).toContain('Submit Form')
|
|
2160
|
+
|
|
2161
|
+
// Verify refToElement map is populated
|
|
2162
|
+
expect(ariaResult.refToElement.size).toBeGreaterThan(0)
|
|
2163
|
+
console.log('RefToElement map size:', ariaResult.refToElement.size)
|
|
2164
|
+
console.log('RefToElement entries:', [...ariaResult.refToElement.entries()])
|
|
2165
|
+
|
|
2166
|
+
// Verify we can select elements using aria-ref selectors
|
|
2167
|
+
const btnViaAriaRef = cdpPage!.locator('aria-ref=e2')
|
|
2168
|
+
const btnTextViaRef = await btnViaAriaRef.textContent()
|
|
2169
|
+
console.log('Button text via aria-ref=e2:', btnTextViaRef)
|
|
2170
|
+
expect(btnTextViaRef).toBe('Submit Form')
|
|
2171
|
+
|
|
2172
|
+
// Get ref for the submit button using getRefForLocator
|
|
2173
|
+
const submitBtn = cdpPage!.locator('#submit-btn')
|
|
2174
|
+
const btnAriaRef = await ariaResult.getRefForLocator(submitBtn)
|
|
2175
|
+
console.log('Button ariaRef:', btnAriaRef)
|
|
2176
|
+
expect(btnAriaRef).toBeDefined()
|
|
2177
|
+
expect(btnAriaRef?.role).toBe('button')
|
|
2178
|
+
expect(btnAriaRef?.name).toBe('Submit Form')
|
|
2179
|
+
expect(btnAriaRef?.ref).toMatch(/^e\d+$/)
|
|
2180
|
+
|
|
2181
|
+
// Verify the ref matches what we can use to select
|
|
2182
|
+
const btnFromRef = cdpPage!.locator(`aria-ref=${btnAriaRef?.ref}`)
|
|
2183
|
+
const btnText = await btnFromRef.textContent()
|
|
2184
|
+
expect(btnText).toBe('Submit Form')
|
|
2185
|
+
|
|
2186
|
+
// Test getRefStringForLocator
|
|
2187
|
+
const btnRefStr = await ariaResult.getRefStringForLocator(submitBtn)
|
|
2188
|
+
console.log('Button ref string:', btnRefStr)
|
|
2189
|
+
expect(btnRefStr).toBe(btnAriaRef?.ref)
|
|
2190
|
+
|
|
2191
|
+
// Test link
|
|
2192
|
+
const aboutLink = cdpPage!.locator('a')
|
|
2193
|
+
const linkAriaRef = await ariaResult.getRefForLocator(aboutLink)
|
|
2194
|
+
console.log('Link ariaRef:', linkAriaRef)
|
|
2195
|
+
expect(linkAriaRef).toBeDefined()
|
|
2196
|
+
expect(linkAriaRef?.role).toBe('link')
|
|
2197
|
+
expect(linkAriaRef?.name).toBe('About Us')
|
|
2198
|
+
|
|
2199
|
+
// Verify the link ref works
|
|
2200
|
+
const linkFromRef = cdpPage!.locator(`aria-ref=${linkAriaRef?.ref}`)
|
|
2201
|
+
const linkText = await linkFromRef.textContent()
|
|
2202
|
+
expect(linkText).toBe('About Us')
|
|
2203
|
+
|
|
2204
|
+
// Test input field
|
|
2205
|
+
const inputField = cdpPage!.locator('input')
|
|
2206
|
+
const inputAriaRef = await ariaResult.getRefForLocator(inputField)
|
|
2207
|
+
console.log('Input ariaRef:', inputAriaRef)
|
|
2208
|
+
expect(inputAriaRef).toBeDefined()
|
|
2209
|
+
expect(inputAriaRef?.role).toBe('textbox')
|
|
2210
|
+
|
|
2211
|
+
// Test batch getRefsForLocators - single evaluate call for multiple elements
|
|
2212
|
+
const batchRefs = await ariaResult.getRefsForLocators([submitBtn, aboutLink, inputField])
|
|
2213
|
+
console.log('Batch refs:', batchRefs)
|
|
2214
|
+
expect(batchRefs).toHaveLength(3)
|
|
2215
|
+
expect(batchRefs[0]?.ref).toBe(btnAriaRef?.ref)
|
|
2216
|
+
expect(batchRefs[1]?.ref).toBe(linkAriaRef?.ref)
|
|
2217
|
+
expect(batchRefs[2]?.ref).toBe(inputAriaRef?.ref)
|
|
2218
|
+
|
|
2219
|
+
await browser.close()
|
|
2220
|
+
await page.close()
|
|
2221
|
+
}, 60000)
|
|
2222
|
+
|
|
2223
|
+
it('should show aria ref labels on real pages and save screenshots', async () => {
|
|
2224
|
+
const browserContext = getBrowserContext()
|
|
2225
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
2226
|
+
|
|
2227
|
+
const { showAriaRefLabels, hideAriaRefLabels } = await import('./aria-snapshot.js')
|
|
2228
|
+
const fs = await import('node:fs')
|
|
2229
|
+
const path = await import('node:path')
|
|
2230
|
+
|
|
2231
|
+
// Create assets folder for screenshots
|
|
2232
|
+
const assetsDir = path.join(path.dirname(new URL(import.meta.url).pathname), 'assets')
|
|
2233
|
+
if (!fs.existsSync(assetsDir)) {
|
|
2234
|
+
fs.mkdirSync(assetsDir, { recursive: true })
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
const testPages = [
|
|
2238
|
+
{ name: 'hacker-news', url: 'https://news.ycombinator.com/' },
|
|
2239
|
+
{ name: 'google', url: 'https://www.google.com/' },
|
|
2240
|
+
{ name: 'github', url: 'https://github.com/' },
|
|
2241
|
+
]
|
|
2242
|
+
|
|
2243
|
+
// Create all pages and enable extension for each
|
|
2244
|
+
const pages = await Promise.all(
|
|
2245
|
+
testPages.map(async ({ name, url }) => {
|
|
2246
|
+
const page = await browserContext.newPage()
|
|
2247
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' })
|
|
2248
|
+
return { name, url, page }
|
|
2249
|
+
})
|
|
2250
|
+
)
|
|
2251
|
+
|
|
2252
|
+
// Enable extension for each tab (must be done sequentially as it uses active tab)
|
|
2253
|
+
for (const { page } of pages) {
|
|
2254
|
+
await page.bringToFront()
|
|
2255
|
+
await serviceWorker.evaluate(async () => {
|
|
2256
|
+
await globalThis.toggleExtensionForActiveTab()
|
|
2257
|
+
})
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// Connect CDP and process all pages concurrently
|
|
2261
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
2262
|
+
|
|
2263
|
+
await Promise.all(
|
|
2264
|
+
pages.map(async ({ name, url, page }) => {
|
|
2265
|
+
const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes(new URL(url).hostname))
|
|
2266
|
+
|
|
2267
|
+
if (!cdpPage) {
|
|
2268
|
+
console.log(`Could not find CDP page for ${name}, skipping...`)
|
|
2269
|
+
return
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// Show aria ref labels
|
|
2273
|
+
const { snapshot, labelCount } = await showAriaRefLabels({ page: cdpPage })
|
|
2274
|
+
console.log(`${name}: ${labelCount} labels shown`)
|
|
2275
|
+
expect(labelCount).toBeGreaterThan(0)
|
|
2276
|
+
|
|
2277
|
+
// Take screenshot with labels visible
|
|
2278
|
+
const screenshot = await cdpPage.screenshot({ type: 'png', fullPage: false })
|
|
2279
|
+
const screenshotPath = path.join(assetsDir, `aria-labels-${name}.png`)
|
|
2280
|
+
fs.writeFileSync(screenshotPath, screenshot)
|
|
2281
|
+
console.log(`Screenshot saved: ${screenshotPath}`)
|
|
2282
|
+
|
|
2283
|
+
// Save snapshot text for reference
|
|
2284
|
+
const snapshotPath = path.join(assetsDir, `aria-labels-${name}-snapshot.txt`)
|
|
2285
|
+
fs.writeFileSync(snapshotPath, snapshot)
|
|
2286
|
+
|
|
2287
|
+
// Verify labels are in DOM
|
|
2288
|
+
const labelElements = await cdpPage.evaluate(() =>
|
|
2289
|
+
document.querySelectorAll('.__pw_label__').length
|
|
2290
|
+
)
|
|
2291
|
+
expect(labelElements).toBe(labelCount)
|
|
2292
|
+
|
|
2293
|
+
// Cleanup
|
|
2294
|
+
await hideAriaRefLabels({ page: cdpPage })
|
|
2295
|
+
|
|
2296
|
+
// Verify labels removed
|
|
2297
|
+
const labelsAfterHide = await cdpPage.evaluate(() =>
|
|
2298
|
+
document.getElementById('__playwriter_labels__')
|
|
2299
|
+
)
|
|
2300
|
+
expect(labelsAfterHide).toBeNull()
|
|
2301
|
+
|
|
2302
|
+
await page.close()
|
|
2303
|
+
})
|
|
2304
|
+
)
|
|
2305
|
+
|
|
2306
|
+
await browser.close()
|
|
2307
|
+
console.log(`Screenshots saved to: ${assetsDir}`)
|
|
2308
|
+
}, 120000)
|
|
2309
|
+
|
|
1957
2310
|
})
|
|
1958
2311
|
|
|
1959
2312
|
|
|
@@ -2881,3 +3234,69 @@ describe('CDP Session Tests', () => {
|
|
|
2881
3234
|
await page.close()
|
|
2882
3235
|
}, 60000)
|
|
2883
3236
|
})
|
|
3237
|
+
|
|
3238
|
+
describe('Auto-enable Tests', () => {
|
|
3239
|
+
let testCtx: TestContext | null = null
|
|
3240
|
+
|
|
3241
|
+
// Set env var before any setup runs
|
|
3242
|
+
process.env.PLAYWRITER_AUTO_ENABLE = '1'
|
|
3243
|
+
|
|
3244
|
+
beforeAll(async () => {
|
|
3245
|
+
testCtx = await setupTestContext({ tempDirPrefix: 'pw-auto-test-' })
|
|
3246
|
+
|
|
3247
|
+
// Disconnect all tabs to start with a clean state
|
|
3248
|
+
const serviceWorker = await getExtensionServiceWorker(testCtx.browserContext)
|
|
3249
|
+
await serviceWorker.evaluate(async () => {
|
|
3250
|
+
await globalThis.disconnectEverything()
|
|
3251
|
+
})
|
|
3252
|
+
await new Promise(r => setTimeout(r, 100))
|
|
3253
|
+
}, 600000)
|
|
3254
|
+
|
|
3255
|
+
afterAll(async () => {
|
|
3256
|
+
delete process.env.PLAYWRITER_AUTO_ENABLE
|
|
3257
|
+
await cleanupTestContext(testCtx)
|
|
3258
|
+
testCtx = null
|
|
3259
|
+
})
|
|
3260
|
+
|
|
3261
|
+
const getBrowserContext = () => {
|
|
3262
|
+
if (!testCtx?.browserContext) throw new Error('Browser not initialized')
|
|
3263
|
+
return testCtx.browserContext
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
it('should auto-create a tab when Playwright connects and no tabs exist', async () => {
|
|
3267
|
+
const browserContext = getBrowserContext()
|
|
3268
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
3269
|
+
|
|
3270
|
+
// Verify no tabs are connected
|
|
3271
|
+
const tabCountBefore = await serviceWorker.evaluate(() => {
|
|
3272
|
+
const state = globalThis.getExtensionState()
|
|
3273
|
+
return state.tabs.size
|
|
3274
|
+
})
|
|
3275
|
+
expect(tabCountBefore).toBe(0)
|
|
3276
|
+
|
|
3277
|
+
// Connect Playwright - this should trigger auto-create
|
|
3278
|
+
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
|
3279
|
+
|
|
3280
|
+
// Verify a page was auto-created
|
|
3281
|
+
const pages = browser.contexts()[0].pages()
|
|
3282
|
+
expect(pages.length).toBeGreaterThan(0)
|
|
3283
|
+
expect(pages.length).toBe(1)
|
|
3284
|
+
|
|
3285
|
+
const autoCreatedPage = pages[0]
|
|
3286
|
+
expect(autoCreatedPage.url()).toBe('about:blank')
|
|
3287
|
+
|
|
3288
|
+
// Verify extension state shows the tab as connected
|
|
3289
|
+
const tabCountAfter = await serviceWorker.evaluate(() => {
|
|
3290
|
+
const state = globalThis.getExtensionState()
|
|
3291
|
+
return state.tabs.size
|
|
3292
|
+
})
|
|
3293
|
+
expect(tabCountAfter).toBe(1)
|
|
3294
|
+
|
|
3295
|
+
// Verify we can interact with the auto-created page
|
|
3296
|
+
await autoCreatedPage.setContent('<h1>Auto-created page</h1>')
|
|
3297
|
+
const title = await autoCreatedPage.locator('h1').textContent()
|
|
3298
|
+
expect(title).toBe('Auto-created page')
|
|
3299
|
+
|
|
3300
|
+
await browser.close()
|
|
3301
|
+
}, 60000)
|
|
3302
|
+
})
|