Skip to content

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

Closed

Description

@jasonjtd

What's going on?

Summary

MCP Client: Claude

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:

  1. browser_navigate to a normal login page — OK on both versions.
  2. Submit a login that lands on the <frameset> app shell.
  3. 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.

Version

0.0.78

Activity

  1. github-actions commented on Jul 15, 2026

    @github-actions
    Contributor

    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>');
      });
    
      await page.goto(server.PREFIX + '/frameset', { waitUntil: 'commit' });
    
      // Raw evaluate is instant.
      const t0 = Date.now();
      await page.evaluate(() => location.href);
      expect(Date.now() - t0).toBeLessThan(3000); // passes (~a few ms)
    
      // AI-mode snapshot hangs until the timeout.
      const t1 = Date.now();
      await page.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.

    Triaged by the Playwright bot - agent run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions