You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Regression: every tool call hangs 30s (browserBackend.callTool: Timeout 30000ms) on a classic <frameset> page whose load never fires — 0.0.78 vs 0.0.76 #41784
On a classic HTML <frameset> page that contains a persistent child frame (a long-poll / SSE
connection that keeps the top document's load event from ever firing), every tool call in @playwright/[email protected] fails with browserBackend.callTool: Timeout 30000ms exceeded — including a
trivial browser_evaluate that just reads location.href.
The same page and same steps work instantly on @playwright/[email protected]. This is a regression.
Environment
Working
Broken
@playwright/mcp
0.0.76
0.0.78
bundled playwright-core
1.61.0-alpha
1.62.0-alpha
OS
Windows 11
Windows 11
Browser
Chrome (channel chrome)
same
Args
--isolated
--isolated
Reproduction
Any page that (a) is a classic <frameset> and (b) never fires the top load event because a child
frame holds an open long-poll / EventSource (SSE) connection. Then call any tool:
browser_navigate to a normal login page — OK on both versions.
Submit a login that lands on the <frameset> app shell.
Call browser_evaluate with () => ({ url: location.href }).
Expected: returns quickly (the DOM is fully parsed and readable; raw Playwright page.evaluate
returns instantly).
Actual (0.0.78): the call hangs and returns ### Error TimeoutError: browserBackend.callTool: Timeout 30000ms exceeded.
Every subsequent tool call does the same.
A/B evidence (identical steps, only the pinned version differs)
@playwright/[email protected]: submit login: 30254ms HANG in-frame read: 30528ms HANG
@playwright/[email protected]: submit login: 268ms OK in-frame read: 2092ms OK
→ { top: "POV_TXP_MAIN.aspx?...", hasContentDoc: true, contentUrl: "..." }
Suspected cause
The 0.0.78 release notes include “Accessibility snapshots are distilled to reduce noise and token
usage.” On 0.0.76 the snapshot appears to stop at the frameset shell (fast); on 0.0.78 the distillation
pass seems to walk into the frame subtree — including the never-settling long-poll frame — and blocks
until the 30s backend timeout. Raw Playwright (page.evaluate, page.screenshot) has no such issue on
the same page, so it is specific to the MCP’s per-response snapshot/backend step, not to Playwright core.
Notes
--snapshot-mode none does not mitigate it (still hangs), so it isn’t bypassed by disabling the
response snapshot alone.
Raw Playwright drives the same page fully (reads, clicks into frames via frameLocator, screenshots
in ~80ms). The hang is only via the MCP tool layer.
Request
Restore bounded/shell-stopping snapshot behavior (or make the distillation time-bounded and resilient to
frames that never reach load), so tool calls don’t hang on classic frameset apps.
Hi, I'm the Playwright bot and I took a first look at this.
Reproduced on tip-of-tree (npm 1.62.0-next, sha 3c71159). This is a real bug, and your diagnosis is right: on a classic <frameset> where a child frame holds a connection open so the top load never fires, the AI-mode aria snapshot hangs until the timeout — while raw page.evaluate returns instantly. That snapshot runs on every MCP tool response, which is why every call times out at 30s.
Root cause is in playwright-core, not the MCP layer specifically. The mode: 'ai' snapshot recurses into child iframes (ariaSnapshotForFrame in packages/playwright-core/src/server/page.ts) and polls each child frame's body with retryWithProgressAndTimeouts([1000, 2000, 4000, 8000]). A frame whose document never commits/settles never resolves, so the recursion keeps polling until the overall deadline. The default/locator snapshot doesn't descend into iframes, which is why 0.0.76 (bundled 1.61) stopped at the frameset shell and was fast — the iframe distillation in 1.62 is what walks into the never-settling frame. --snapshot-mode none doesn't help because the tool backend still captures a snapshot for its own bookkeeping.
Minimal repro (tests/page, npm run ctest)
import{test,expect}from'./pageTest';test('ai snapshot hangs on frameset with never-loading child frame',async({ page, server })=>{// Child frame that never finishes loading (holds the connection open) ->// the top frameset load never fires.server.setRoute('/hang',(req,res)=>{res.writeHead(200,{'content-type': 'text/html'});res.write('<html><body>hanging');// never res.end()});server.setRoute('/frameset',(req,res)=>{res.writeHead(200,{'content-type': 'text/html'});res.end('<frameset cols="50%,50%"><frame src="/hang"><frame src="about:blank"></frameset>');});awaitpage.goto(server.PREFIX+'/frameset',{waitUntil: 'commit'});// Raw evaluate is instant.constt0=Date.now();awaitpage.evaluate(()=>location.href);expect(Date.now()-t0).toBeLessThan(3000);// passes (~a few ms)// AI-mode snapshot hangs until the timeout.constt1=Date.now();awaitpage.ariaSnapshot({mode: 'ai',timeout: 20000});expect(Date.now()-t1).toBeLessThan(5000);// fails: TimeoutError 20000ms exceeded});
page.evaluate returns in a few ms; page.ariaSnapshot({ mode: 'ai' }) times out. Chromium, headless — matches your setup.
I didn't dig into a fix, and I couldn't A/B every detail of your app, but your 0.0.76-vs-0.0.78 evidence lines up exactly with what I see: the regression is the AI snapshot descending into a frame that never reaches load.
Activity
github-actions commentedon Jul 15, 2026
Hi, I'm the Playwright bot and I took a first look at this.
Reproduced on tip-of-tree (npm
1.62.0-next, sha3c71159). This is a real bug, and your diagnosis is right: on a classic<frameset>where a child frame holds a connection open so the toploadnever fires, the AI-mode aria snapshot hangs until the timeout — while rawpage.evaluatereturns instantly. That snapshot runs on every MCP tool response, which is why every call times out at 30s.Root cause is in playwright-core, not the MCP layer specifically. The
mode: 'ai'snapshot recurses into child iframes (ariaSnapshotForFrameinpackages/playwright-core/src/server/page.ts) and polls each child frame'sbodywithretryWithProgressAndTimeouts([1000, 2000, 4000, 8000]). A frame whose document never commits/settles never resolves, so the recursion keeps polling until the overall deadline. Thedefault/locator snapshot doesn't descend into iframes, which is why 0.0.76 (bundled 1.61) stopped at the frameset shell and was fast — the iframe distillation in 1.62 is what walks into the never-settling frame.--snapshot-mode nonedoesn't help because the tool backend still captures a snapshot for its own bookkeeping.Minimal repro (tests/page,
npm run ctest)page.evaluatereturns in a few ms;page.ariaSnapshot({ mode: 'ai' })times out. Chromium, headless — matches your setup.I didn't dig into a fix, and I couldn't A/B every detail of your app, but your 0.0.76-vs-0.0.78 evidence lines up exactly with what I see: the regression is the AI snapshot descending into a frame that never reaches
load.Triaged by the Playwright bot - agent run