Skip to content

[Regression]: Chromium click on a link answered with 204 No Content times out (1.64) #43279

Closed

Description

Version

1.64.0, and 1.65.0-alpha-2026-10-11. Last good: 1.63.0.

Steps to reproduce

Save as repro.mjs and run with node repro.mjs:

import http from 'node:http';
import { chromium } from 'playwright';

const server = http.createServer((req, res) => {
  if (req.url === '/nothing') { res.writeHead(204); return res.end(); }
  res.writeHead(200, { 'content-type': 'text/html' });
  res.end(req.url === '/framed'
    ? '<iframe src="/frame"></iframe>'
    : '<a id="l" href="/nothing">204 link</a>');
});
await new Promise(r => server.listen(0, '127.0.0.1', r));
const base = `http://127.0.0.1:${server.address().port}`;

const browser = await chromium.launch();
const page = await browser.newPage();
const time = async (name, fn) => {
  const start = Date.now();
  try { await fn(); console.log(name, 'ok', Date.now() - start, 'ms'); }
  catch (e) { console.log(name, 'FAIL', Date.now() - start, 'ms', e.message.split('\n')[0]); }
};

await page.goto(base + '/');
await time('click', () => page.click('#l', { timeout: 8000 }));
await page.goto(base + '/');
await time('click, noWaitAfter', () => page.click('#l', { timeout: 8000, noWaitAfter: true }));
await page.goto(base + '/framed');
await time('click in iframe', () => page.frameLocator('iframe').locator('#l').click({ timeout: 8000 }));

await browser.close();
server.close();

Expected behavior

The click resolves right away, as in 1.63. A navigation answered with 204 No Content is dropped by the browser and never commits, so there is nothing to wait for.

Actual behavior

On Chromium, the top-level click waits until the action's timeout and then throws page.click: Timeout 8000ms exceeded.

Chromium Firefox WebKit
1.63.0 ok, 64 ms
1.64.0 FAIL, timeout ok, 149 ms ok, 176 ms
1.64.0, noWaitAfter: true ok, 57 ms ok, 92 ms ok, 52 ms
1.64.0, the same link inside an iframe ok, 84 ms ok, 115 ms ok, 86 ms
1.65.0-alpha-2026-10-11 FAIL, timeout

It is not the Chromium roll. With channel: 'chrome' against the same installed Google Chrome 154.0.8037.98, 1.63.0 passes in 50 ms and 1.64.0 times out. The change looks to be in how the Chromium driver's after-action wait treats a navigation that is requested but never committed.

Bisected to 1.64.0-alpha-2026-09-22 (last good: 1.64.0-alpha-2026-09-21), i.e. #42834, "report 204 subresource responses as finished" (the fix for #42786). That change also reports a 204 navigation as finished, so the frame's pending navigation is never aborted.

Additional context

It turned up in a tool that clicks links in arbitrary web apps. Links that answer 204 (tracking pixels, "mark as read" links, download endpoints with nothing to send) are common, and on 1.64 every such click now costs the full action timeout.

A likely fix: keep #42834's 204-as-finished path for subresources only, in packages/playwright-core/src/server/chromium/crNetworkManager.ts:

if (response?.status() === 204 && !request.request.isNavigationRequest()) {

A 204 navigation is then reported as failed again, as in 1.63 and as Firefox and WebKit report it, so the pending navigation is aborted. Locally, with this change, a click test and a requestfailed test for a 204 navigation pass on all three browsers.

Environment

macOS 26 (Darwin 25.6.0), arm64
Node.js 24.11.1
playwright 1.64.0 (bundled Chromium 156.0.8078.4); also 1.65.0-alpha-2026-10-11 (Chromium 156.0.8078.12)
Google Chrome 154.0.8037.98 (channel: 'chrome')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions