fix: filter non-page targets from Playwright (issue #14)
Service workers, web workers, and iframes are now filtered out at the server level, preventing Playwright from trying to initialize these targets which caused timeouts and errors.
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## 0.0.44
|
## 0.0.44
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **Filter non-page targets from Playwright (issue #14)**: Service workers, web workers, and other non-page targets are now filtered out at the server level. This prevents Playwright from trying to initialize these targets, which would cause timeouts waiting for `executionContextCreated` events and errors on `Target.detachFromTarget`.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- **Search context lines**: `accessibilitySnapshot`, `getCleanHTML`, and `getLatestLogs` now include 5 lines of context above and below each search match
|
- **Search context lines**: `accessibilitySnapshot`, `getCleanHTML`, and `getLatestLogs` now include 5 lines of context above and below each search match
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
- generic [ref=e55]:
|
- generic [ref=e55]:
|
||||||
- text: "Google offered in:"
|
- text: "Google offered in:"
|
||||||
- link [ref=e56] [cursor=pointer]:
|
- link [ref=e56] [cursor=pointer]:
|
||||||
- /url: https://www.google.com/setprefs?sig=0_-1OPkiTlOBOVFrBWG18fjvZuNsY%3D&hl=it&source=homepage&sa=X&ved=0ahUKEwjstcWBt4SSAxVe4jgGHTucPWEQ2ZgBCBU
|
- /url: https://www.google.com/setprefs?sig=0_naJUnYTqN6Qpp-iLC0lAP9bnktM%3D&hl=it&source=homepage&sa=X&ved=0ahUKEwiW-c_xsoaSAxX5rZUCHdnrMyMQ2ZgBCBU
|
||||||
- text: Italiano
|
- text: Italiano
|
||||||
- contentinfo [ref=e58]:
|
- contentinfo [ref=e58]:
|
||||||
- generic [ref=e59]: Italy
|
- generic [ref=e59]: Italy
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -763,6 +763,15 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
|
|
||||||
if (method === 'Target.attachedToTarget') {
|
if (method === 'Target.attachedToTarget') {
|
||||||
const targetParams = params as Protocol.Target.AttachedToTargetEvent
|
const targetParams = params as Protocol.Target.AttachedToTargetEvent
|
||||||
|
const targetType = targetParams.targetInfo.type
|
||||||
|
|
||||||
|
// Filter out non-page targets (service workers, web workers, etc.)
|
||||||
|
// These targets can't be properly controlled through chrome.debugger API
|
||||||
|
// and cause issues when Playwright tries to initialize them (issue #14)
|
||||||
|
if (targetType !== 'page') {
|
||||||
|
logger?.log(chalk.gray(`[Server] Ignoring non-page target: ${targetType} (${targetParams.targetInfo.url})`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!targetParams.targetInfo.url) {
|
if (!targetParams.targetInfo.url) {
|
||||||
logger?.error(chalk.red('[Extension] WARNING: Target.attachedToTarget received with empty URL!'), JSON.stringify({ method, params: targetParams, sessionId }))
|
logger?.error(chalk.red('[Extension] WARNING: Target.attachedToTarget received with empty URL!'), JSON.stringify({ method, params: targetParams, sessionId }))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ test('formatHtmlForPrompt', async () => {
|
|||||||
"<!doctype html>
|
"<!doctype html>
|
||||||
<html data-redirect-timezone="1">
|
<html data-redirect-timezone="1">
|
||||||
<body>
|
<body>
|
||||||
<div data-framer-hydrate-v2="{"routeId":"Zw20hns9v","localeId":"default","breakpoints":[{"hash":"2ngqvi","mediaQuery":"(min-width: 1200px)"},{"hash":"11ziuji","mediaQuery":"(min-width: 810px) and (max-width: 1199.98px)"},{"hash":...237 more characters" data-framer-ssr-released-at="2026-01-09T18:19:52.935Z" data-framer-page-optimized-at="2026-01-09T18:42:33.421Z" data-framer-generated-page="">
|
<div data-framer-hydrate-v2="{"routeId":"Zw20hns9v","localeId":"default","breakpoints":[{"hash":"2ngqvi","mediaQuery":"(min-width: 1200px)"},{"hash":"11ziuji","mediaQuery":"(min-width: 810px) and (max-width: 1199.98px)"},{"hash":...237 more characters" data-framer-ssr-released-at="2026-01-12T00:23:47.512Z" data-framer-page-optimized-at="2026-01-12T09:49:28.497Z" data-framer-generated-page="">
|
||||||
<div data-layout-template="true" data-selection="true">
|
<div data-layout-template="true" data-selection="true">
|
||||||
<div>
|
<div>
|
||||||
<nav data-framer-name="Desktop Nav" data-hide-scrollbars="true">
|
<nav data-framer-name="Desktop Nav" data-hide-scrollbars="true">
|
||||||
@@ -2546,7 +2546,7 @@ test('formatHtmlForPrompt', async () => {
|
|||||||
<div data-framer-name="UI">
|
<div data-framer-name="UI">
|
||||||
<div data-framer-name="Stats">
|
<div data-framer-name="Stats">
|
||||||
<div data-border="true" data-framer-name="Content">
|
<div data-border="true" data-framer-name="Content">
|
||||||
<p>January 9, 2026</p>
|
<p>January 12, 2026</p>
|
||||||
<div>
|
<div>
|
||||||
<div data-framer-component-type="RichTextContainer">
|
<div data-framer-component-type="RichTextContainer">
|
||||||
<p>Pageviews</p>
|
<p>Pageviews</p>
|
||||||
@@ -2806,7 +2806,7 @@ test('formatHtmlForPrompt', async () => {
|
|||||||
<div data-framer-name="UI">
|
<div data-framer-name="UI">
|
||||||
<div data-framer-name="Stats">
|
<div data-framer-name="Stats">
|
||||||
<div data-border="true" data-framer-name="Content">
|
<div data-border="true" data-framer-name="Content">
|
||||||
<p>January 9, 2026</p>
|
<p>January 12, 2026</p>
|
||||||
<div>
|
<div>
|
||||||
<div data-framer-component-type="RichTextContainer">
|
<div data-framer-component-type="RichTextContainer">
|
||||||
<p>Pageviews</p>
|
<p>Pageviews</p>
|
||||||
@@ -3066,7 +3066,7 @@ test('formatHtmlForPrompt', async () => {
|
|||||||
<div data-framer-name="UI">
|
<div data-framer-name="UI">
|
||||||
<div data-framer-name="Stats">
|
<div data-framer-name="Stats">
|
||||||
<div data-border="true" data-framer-name="Content">
|
<div data-border="true" data-framer-name="Content">
|
||||||
<p>January 9, 2026</p>
|
<p>January 12, 2026</p>
|
||||||
<div>
|
<div>
|
||||||
<div data-framer-component-type="RichTextContainer">
|
<div data-framer-component-type="RichTextContainer">
|
||||||
<p>Pageviews</p>
|
<p>Pageviews</p>
|
||||||
|
|||||||
+27
-29
@@ -3541,18 +3541,21 @@ describe('Service Worker Target Tests', () => {
|
|||||||
return testCtx.browserContext
|
return testCtx.browserContext
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should not disconnect when page has service worker (issue #14)', async () => {
|
it('should not expose service worker targets to Playwright (issue #14)', async () => {
|
||||||
// This test reproduces issue #14: pages with service workers cause disconnection loops.
|
// This test verifies that service worker targets are NOT forwarded to Playwright.
|
||||||
// The problem is that Target.setAutoAttach attaches to service workers, and when
|
// Issue #14: pages with service workers cause problems because Playwright tries to
|
||||||
// Playwright tries to enable Network/Runtime on the SW session, the extension can't
|
// initialize service worker sessions with Runtime.enable/Network.enable, which
|
||||||
// find a matching tab, causing errors and disconnection.
|
// times out waiting for executionContextCreated (service workers don't have main frames).
|
||||||
|
//
|
||||||
|
// The fix is to filter out non-page targets (service_worker, worker, etc.) in the
|
||||||
|
// server so Playwright never sees them.
|
||||||
|
|
||||||
const browserContext = getBrowserContext()
|
const browserContext = getBrowserContext()
|
||||||
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
||||||
|
|
||||||
// Discord has a service worker - navigate there
|
// web.dev has a service worker - navigate there
|
||||||
const page = await browserContext.newPage()
|
const page = await browserContext.newPage()
|
||||||
await page.goto('https://discord.com/login', { waitUntil: 'load' })
|
await page.goto('https://web.dev/', { waitUntil: 'load' })
|
||||||
await page.bringToFront()
|
await page.bringToFront()
|
||||||
|
|
||||||
// Attach extension to the page
|
// Attach extension to the page
|
||||||
@@ -3561,33 +3564,28 @@ describe('Service Worker Target Tests', () => {
|
|||||||
})
|
})
|
||||||
await new Promise(r => setTimeout(r, 500))
|
await new Promise(r => setTimeout(r, 500))
|
||||||
|
|
||||||
// Connect via Playwright CDP - this triggers Target.setAutoAttach which
|
// Connect via Playwright CDP - this triggers Target.setAutoAttach
|
||||||
// will also attach to the service worker
|
|
||||||
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
||||||
|
const context = browser.contexts()[0]
|
||||||
|
|
||||||
// Track disconnection events
|
// Get all targets/pages that Playwright knows about
|
||||||
let disconnected = false
|
const pages = context.pages()
|
||||||
browser.on('disconnected', () => {
|
|
||||||
disconnected = true
|
|
||||||
console.log('Browser disconnected!')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for potential disconnection - issue says ~10s loop
|
// All pages should be actual pages, not service workers or workers
|
||||||
// We wait 15 seconds to be sure
|
for (const p of pages) {
|
||||||
console.log('Waiting 15 seconds to check for disconnection loop...')
|
const url = p.url()
|
||||||
await new Promise(r => setTimeout(r, 15000))
|
console.log('Page URL:', url)
|
||||||
|
// Service workers would have URLs like https://web.dev/sw.js
|
||||||
|
expect(url).not.toMatch(/sw\.js$/i)
|
||||||
|
expect(url).not.toMatch(/service.?worker/i)
|
||||||
|
}
|
||||||
|
|
||||||
// Should still be connected - this is what we're testing
|
// Verify we can interact with the main page
|
||||||
expect(disconnected).toBe(false)
|
const targetPage = pages.find(p => p.url().includes('web.dev'))
|
||||||
expect(browser.isConnected()).toBe(true)
|
expect(targetPage).toBeDefined()
|
||||||
|
|
||||||
// Verify we can still interact with the page
|
const title = await targetPage!.title()
|
||||||
const pages = browser.contexts()[0].pages()
|
expect(title).toBeTruthy()
|
||||||
const discordPage = pages.find(p => p.url().includes('discord.com'))
|
|
||||||
expect(discordPage).toBeDefined()
|
|
||||||
|
|
||||||
const url = await discordPage!.evaluate(() => window.location.href)
|
|
||||||
expect(url).toContain('discord.com')
|
|
||||||
|
|
||||||
await browser.close()
|
await browser.close()
|
||||||
await page.close()
|
await page.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user