Skip to content

[Regression]: middle / modifier click intermittently fails to open a new tab on Playwright 1.62.x #42142

Description

Last Good Version

1.61.1

First Bad Version

1.62.0

Steps to reproduce

  1. git clone https://github.com/YuriiMotov/playwright-1.62-new-tab-mre.git
  2. cd playwright-1.62-new-tab-mre
  3. npm install
  4. npx playwright --version - expected Version 1.62.1
  5. npx playwright test --repeat-each=25 --workers=16 - see several tests fail with Test timeout of 30000ms exceeded
  6. Downgrade to latest working version: npm pkg set devDependencies.@playwright/test=1.61.1 && npm install
  7. npx playwright --version - expected Version 1.61.1
  8. npx playwright test --repeat-each=25 --workers=16 - see tests pass

Expected behavior

I expect tests pass on 1.62.x as it was on 1.61.1 and previous versions.

Actual behavior

Several tests fail with Test timeout of 30000ms exceeded

Additional context

Only tests that attempt to open page in a new tab are affected.

--workers=16 here is to imitate overloaded machine. In fact in our case it fails with --workers=1 in CI.

See report generated by Claude Code: https://github.com/YuriiMotov/playwright-1.62-new-tab-mre/blob/master/README.md

Environment

System:
    OS: Linux 7.0 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (14) x64 Intel(R) Core(TM) Ultra 5 125U
    Memory: 28.49 GB / 46.52 GB
    Container: Yes
  Binaries:
    Node: 22.23.0 - /run/user/1000/fnm_multishells/1402339_1785955498713/bin/node
    npm: 10.9.8 - /run/user/1000/fnm_multishells/1402339_1785955498713/bin/npm
    bun: 1.3.8 - /home/yurii/.bun/bin/bun
  IDEs:
    VSCode: 1.124.2 - /usr/bin/code
  Languages:
    Bash: 5.2.21 - /usr/bin/bash
  npmPackages:
    @playwright/test: 1.62.1 => 1.62.1

Activity

  1. Mahnoor-Zaffar commented on Aug 6, 2026

    @Mahnoor-Zaffar

    Hey! Thanks for reporting this.

    The most likely root cause of this issue appears to be related to changes made in version 1.62.0 that affect the handling of new tab openings. While the specific code changes are not detailed in the issue, the problem is isolated to the functionality that manages new tab interactions. The affected code may reside in the page.ts file or related modules that handle browser interactions.

    A reasonable direction for fixing this issue would be to investigate the changes made in version 1.62.0 that relate to tab management and ensure that the logic for handling middle and modifier clicks is functioning as intended.

    If no one is currently working on this, I'd be happy to take a look and put together a fix.

  2. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor

    Thanks for the exceptionally clean MRE — the version table and the "no JavaScript, pure browser default action" framing made this easy to confirm.

    Reproduced on 1.62.1, and it looks already fixed on tip-of-tree.

    What I ran:

    • 1.62.1 — your MRE unchanged, --repeat-each=25 --workers=16 on a 4-core box (heavy oversubscription): 2/50 failed with waitForURL: Test timeout of 30000ms exceeded. Matches your report.
    • tip-of-tree (1.63.0-next) — the same scenario condensed to a self-contained spec (middle-click and ControlOrMeta click, plain <a href>, context.waitForEvent('page') + waitForURL): 0 failures across 50 tests @ 16 workers and 100 tests @ 32 workers (8× oversubscription). Could not get it to fail.

    Both runs use the same default headless-shell binary, so it's an apples-to-apples comparison.

    The relevant difference is the bundled browser: 1.62.x ships Chromium chromium-1234, tip-of-tree ships chromium-1237. This is a browser-default-action bug (opening a background tab with nothing in the page to interfere), and it disappears with the newer Chromium roll — consistent with an upstream fix that came in after 1.62.x was cut.

    So: nothing to change in the Playwright test code — the intermittent "new tab never opens / never navigates" behavior is gone on the current Chromium. It should ride out with the next release that carries chromium-1237. If you can, giving @playwright/test@next a spin against your real app would be a useful confirmation from your side.

    Triaged by the Playwright bot - agent run

  3. YuriiMotov commented on Aug 6, 2026

    @YuriiMotov
    Author

    I've just tested it with 1.63.0-alpha-2026-08-06 and it still fails:

    $ npm i -D @playwright/test@next
    
    changed 3 packages, and audited 4 packages in 35s
    found 0 vulnerabilities
    
    $ npx playwright install
    
    Downloading Chrome for Testing 152.0.7977.8 (playwright chromium v1237) from https://cdn.playwright.dev/builds/cft/152.0.7977.8/linux64/chrome-linux64.zip
    184.7 MiB [====================] 100% 0.0s
    Chrome for Testing 152.0.7977.8 (playwright chromium v1237) downloaded to ...
    ...
    
    $ npx playwright --version
    
    Version 1.63.0-alpha-2026-08-06
    
    $ npx playwright test --repeat-each=25 --workers=16
    
    ...
      2 failed
        [chromium] › tests/new-tab.spec.ts:3:5 › middle click opens a new tab ──────────────────────────
        [chromium] › tests/new-tab.spec.ts:3:5 › middle click opens a new tab ──────────────────────────
      48 passed (33.5s)
    
    
    npx envinfo --preset playwright
      System:
        OS: Linux 7.0 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
        CPU: (14) x64 Intel(R) Core(TM) Ultra 5 125U
        Memory: 29.22 GB / 46.52 GB
        Container: Yes
      Binaries:
        Node: 22.23.0 - /run/user/1000/fnm_multishells/308728_1786008511663/bin/node
        npm: 10.9.8 - /run/user/1000/fnm_multishells/308728_1786008511663/bin/npm
        bun: 1.3.8 - /home/yurii/.bun/bin/bun
      IDEs:
        VSCode: 1.124.2 - /usr/bin/code
      Languages:
        Bash: 5.2.21 - /usr/bin/bash
      npmPackages:
        @playwright/test: ^1.63.0-alpha-2026-08-06 => 1.63.0-alpha-2026-08-06 
    
  4. github-actions commented on Aug 6, 2026

    @github-actions
    Contributor

    Hi, I'm the Playwright bot and I took another look — thanks for coming back with the @next numbers.

    This is a live regression, still present on tip-of-tree. I can confirm it, and I'm correcting the earlier triage on this thread: it is not fixed by the newer Chromium roll. Clean on 1.61.1, broken from 1.62.0 onward, and still broken on 1.63.0-alpha with chromium-1237.

    What I ran (your MRE, unchanged, on a 4-core box — heavy oversubscription at 16 workers)
    @playwright/test Chromium Result
    1.61.1 headless-shell v1228 0 / 300 failed (3× --repeat-each=50)
    1.62.1 headless-shell v1234 2 / 50 failed (--repeat-each=25)
    1.63.0-alpha (@next) headless-shell v1237 8 / 300 failed (3× --repeat-each=50)

    Failure is the one you reported — browserContext.waitForEvent('page') / waitForURL hangs until the 30s deadline; the background tab either never opens or never navigates. It's timing-dependent, so a single 50-test run often passes clean — it takes repetition under load to surface, which is why the earlier "0 failures on ToT" run was misleading.

    Command: PLAYWRIGHT_HTML_OPEN=never npx playwright test --repeat-each=50 --workers=16

    Condensed self-contained repro

    Same trigger as your MRE, boiled down to our fixtures (still needs load/parallelism to surface — one run passes):

    test('middle click opens a background tab', {
      annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42142' }
    }, async ({ page, context, server }) => {
      server.setRoute('/', (req, res) => {
        res.writeHead(200, { 'content-type': 'text/html' });
        res.end('<a href="/target.html">Open target</a>');
      });
      server.setRoute('/target.html', (req, res) => {
        res.writeHead(200, { 'content-type': 'text/html' });
        res.end('<h1>Target</h1>');
      });
      await page.goto(server.PREFIX + '/');
      const popupPromise = context.waitForEvent('page');
      await page.getByRole('link', { name: 'Open target' }).click({ button: 'middle' });
      await (await popupPromise).waitForURL('**/target.html');
    });

    Since 1.61.1 never fails across the full range you tried and the break lands squarely on 1.62.0, this looks like a real regression on our side that the Chromium update didn't touch. I'm a first pass, so I've left the root cause for a maintainer rather than guessing — but the boundary is clear and the repro is solid.

    Triaged by the Playwright bot.

    Triaged by the Playwright bot - agent run

  5. alirezaedalat commented on Aug 6, 2026

    @alirezaedalat

    Hi, I’d like to investigate this regression.

    My plan is to reproduce it using the provided repository, compare the behavior
    between 1.61.1 and 1.62.x, and identify whether the failure is related to input
    dispatch, modifier handling, or new-page event synchronization.

    If I can isolate the regression, I’ll add a focused regression test and propose
    a minimal fix.

    Please let me know if someone is already working on this.

    Thanks.

  6. Skn0tt commented on Aug 7, 2026

    @Skn0tt
    Contributor
  7. shleder commented on Aug 9, 2026

    @shleder

    A possible optional aid for this regression environment comparison: ReproProof v0.4.0 can emit a redacted, verifiable receipt for a single Playwright run, so the 1.61.1→1.62.x regression conditions can be compared without sharing private paths. A normal minimal reproduction remains required; the receipt is supplemental only. If someone is willing to run one independent trial, please execute reproproof verify --no-replay first and share only the verification result—never raw JSON, secrets, or private paths. No adoption commitment requested; feedback/tracking is at shleder/reproproof#18.

  8. daddyman commented on Sep 1, 2026

    @daddyman

    We have had this problem intermittently for a long time, before 1.60, using a control-click to open a new tab. Random failures to navigate occurred a few times a week.

    The change we made was to not use the headless shell. In playwright.config.ts we added this to all the projects:

    channel: 'chromium', // full chromium, not the headless shell

    Since then, we have not had a new tab failure for over a month.

  9. illusionfield commented on Sep 22, 2026

    @illusionfield

    We see what looks like another symptom of this regression: on Linux, the renderer of the background tab that a modifier click opens crashes with SIGSEGV when the browser context is closed.

    Environment

    • playwright-core / @playwright/test 1.63.0, chromium-headless-shell v1243 (Chromium 153.0.8010.12)
    • Linux arm64 (Debian bookworm container, Node 24) and Linux x86_64 (CI, Docker runner)
    • core dumps enabled (ulimit -c unlimited)

    What we see

    • An e2e suite (1 worker, all tests green, no retries) leaves three core dumps per run on both architectures. They come from the three tests that click an <a href> with modifiers: ['ControlOrMeta'] and then end: when those clicks no longer open a tab, the dumps are gone.
    • Each core is a renderer process: SIGSEGV, si_code 1 (SEGV_MAPERR), fault address 0x568, i.e. a field read through a NULL pointer. Identical on arm64 and x86_64.
    • The crashed renderer's memory holds no URL of the page under test, only about:blank: the background tab never navigated, which matches the "tab never opens / never navigates" failure in this issue.
    • The core file is already there right after context.close(), before browser.close().

    Minimal repro (no test runner, a single link):

    // Run from an empty directory with `ulimit -c unlimited`: with the default
    // relative core_pattern the renderer's core file lands in that directory.
    const http = require('node:http');
    const { chromium } = require('playwright-core');
    
    (async () => {
      const server = http.createServer((req, res) => {
        res.writeHead(200, { 'content-type': 'text/html' });
        res.end('<a id="tile" href="#/next">tile</a>');
      });
      await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
      const base = `http://127.0.0.1:${server.address().port}`;
      const browser = await chromium.launch();
      const context = await browser.newContext();
      const page = await context.newPage();
      await page.goto(`${base}/`);
      await page.locator('#tile').click({ modifiers: ['ControlOrMeta'] });
      await page.waitForTimeout(250);
      await context.close(); // the background tab's renderer crashes here
      await new Promise((resolve) => setTimeout(resolve, 2000));
      await browser.close();
      server.close();
    })();

    Results of this snippet on linux/arm64, 3 runs each (for the channel: 'chromium' row only the launch() call changes):

    Build Renderer core dumps
    1.63.0, headless shell v1243 (Chromium 153.0.8010.12) 3/3
    1.63.0, channel: 'chromium' (Chromium 153.0.8010.12) 0/3
    1.61.1, headless shell v1228 (Chromium 149.0.7827.0) 0/3

    So only the headless shell crashes, and 1.61.1 does not: the same boundary as this issue.

    In our runs nothing crashed without the modifier click (no click, a plain click, or a second page from context.newPage()). Closing the new tab before the context is not a reliable workaround: depending on the page and the timing it avoided the crash in some runs and not in others.

    What we use instead: the test sends the real modifier click with a window-level bubble listener in place, which records event.defaultPrevented after the page's own handlers and then calls preventDefault(), so no tab opens; the test asserts that the page left the click to the browser.

  10. CoJoA13 commented on Sep 26, 2026

    @CoJoA13

    Some data that may help narrow down the stuck step. In our case the tab does open and commit; Playwright just never finishes setting it up.

    Setup: Playwright 1.63.0, default headless shell (chromium-1243, Chromium 153.0.8010.12), Ubuntu x86_64, 16 cores. Ctrl+click (click({ modifiers: ['Control'] })) on a plain <a href> in a Next.js production build. Tests run one at a time, with no extra load.

    Rate: 5 of 60 clicks (about 8%) in a loop that repeats the same click. An earlier run was 3 of 60.

    In every miss:

    • The browser's own target list (Target.getTargets on a browser.newBrowserCDPSession()) shows exactly one new type: "page" target in the context:
      • attached: true;
      • the expected URL;
      • the app's document title, which Chromium shows only after the navigation commits. Before that it reports the pending URL.
    • context.pages().length stays at 1, and no page event arrives, even after 45 s.
    • The tab's video recording has already started and shows the rendered page.

    Closing it reveals the wait. When we close the stranded tab with Target.closeTarget, Playwright emits the context page event for it 6–7 ms later (already closed), immediately followed by close. This happened 5 of 5 times. That looks like the page's initialization was blocked on an await with no timeout until the session was disposed. Could it be the same kind of unbounded wait on a renderer reply that #42883 and #42930 handle for connectOverCDP?

    How this differs from the about:blank crash reported above: there the tab never navigated. Here the tab commits; only Playwright's initialization of it hangs.

    Workaround we use, until there is a fix:

    1. Snapshot the page targets before the click.
    2. Poll Target.getTargets for exactly one new page target with the expected path and title.
    3. Close it with Target.closeTarget.

    We don't use setAutoAttach or setDiscoverTargets. It passed 60/60 on the same clicks where the page event missed 5.

  11. gcharang commented on Sep 30, 2026

    @gcharang

    Another data point from before the 1.62.0 boundary: the same miss happens on 1.60.0 with the default headless shell, chrome-headless-shell 148.0.7778.96 (revision 1223), which predates the Chromium CL bisected above. The pw:protocol logs show where page initialization stops.

    Setup: Linux x86_64 (Ubuntu 24.04), 2 workers, no retries. The whole run (runner, browser and the local static server) was pinned to 2 cores with taskset -c 0,1, with a host load average of 4–10. The test Ctrl+clicks a plain same-origin <a href="/page/#fragment"> with click({ modifiers: ['Control'] }) and waits on context.waitForEvent('page').

    Rates:

    • 19 of 600 runs of the real test, across three builds of the same page.
    • Probes that wait 8 s for the page event:
      • 3/100 from the full page;
      • 4/100 from a bare opener made with page.setContent('<a id="go" href="…">…</a>');
      • 1/100 when that bare link opens a text/plain file, so no page script runs in either tab.

    Where it stops: I logged 180 new tabs with DEBUG=pw:protocol. Page.getFrameTree reported the main frame URL as about:blank for 127 tabs, as the already committed URL for 31, and as ":" for 22.

    • All 7 failures are the 7 ":" tabs that never received Page.frameNavigated.
    • The other 15 ":" tabs passed because Page.frameNavigated arrived in the same millisecond as the Runtime.runIfWaitingForDebugger response, 70–132 ms after attach.
    • In the failing tabs the new document still commits: Runtime.executionContextsCleared/Created for the page's origin arrive 6–60 ms later, the subresources load, and the new loader gets its DOMContentLoaded and load lifecycle events. But Page.frameNavigated for that frame never arrives on any session, and the target stays attached until the context closes.

    When Page.getFrameTree reports ":", FrameSession._initialize treats the frame as the initial empty page and adds _firstNonInitialNavigationCommittedPromise to the promises it awaits. Only a non-initial Page.frameNavigated resolves that promise, so the page is never reported.

    That answers Colton Jones (@CoJoA13)'s question above: this promise is the unbounded wait. Disposing the session rejects it, which fits the page event arriving just before close when the target is closed.

    Failing tab, from its session (URLs replaced; network body fetches and Playwright's own evaluate calls left out):

    +    0ms EVENT Target.attachedToTarget {"type":"page","url":"","waitingForDebugger":true}
    +    8ms SEND  Page.enable / Page.getFrameTree / Log.enable / Page.setLifecycleEventsEnabled / Runtime.enable / Page.addScriptToEvaluateOnNewDocument / Network.enable / Target.setAutoAttach / … / Runtime.runIfWaitingForDebugger
    +   11ms EVENT Page.frameStartedNavigating {"url":"http://127.0.0.1:PORT/page/#fragment","loaderId":"F57C9F7A","navigationType":"differentDocument"}
    +   13ms EVENT Network.requestWillBeSent {"loaderId":"F57C9F7A","type":"Document"}
    +   36ms EVENT Network.responseReceived {"loaderId":"F57C9F7A","type":"Document","status":200}
    +   71ms RESP  Page.enable
    +   71ms RESP  Page.getFrameTree {"frame":{"loaderId":"9DE2AECD","url":":","securityOrigin":"://"}}
    +   72ms SEND  Page.createIsolatedWorld
    +   72ms EVENT Page.lifecycleEvent {"loaderId":"9DE2AECD","name":"commit"}   (+ DOMContentLoaded, networkAlmostIdle, networkIdle for 9DE2AECD)
    +   77ms RESP  Page.setLifecycleEventsEnabled / Runtime.enable / … / Runtime.runIfWaitingForDebugger   (every init command answered)
    +   77ms EVENT Page.lifecycleEvent {"loaderId":"F57C9F7A","name":"init"}
    +   87ms EVENT Page.frameStartedLoading
    +  107ms EVENT Runtime.executionContextsCleared
    +  107ms EVENT Runtime.executionContextCreated {"origin":"http://127.0.0.1:PORT","isDefault":false}
    +  107ms EVENT Runtime.executionContextCreated {"origin":"http://127.0.0.1:PORT","isDefault":true}
    +  446ms EVENT Page.lifecycleEvent {"loaderId":"F57C9F7A","name":"DOMContentLoaded"}
    +  468ms EVENT Page.lifecycleEvent {"loaderId":"F57C9F7A","name":"load"}
    +  468ms EVENT Page.frameStoppedLoading
    + 2038ms EVENT Page.lifecycleEvent {"loaderId":"F57C9F7A","name":"networkIdle"}
    +28295ms EVENT Target.detachedFromTarget            (context closed by the test timeout; no Page.frameNavigated for this frame anywhere in the log)
    

    On 1.62 and later, Chromium reports "" instead of ":" for the initial empty document, and #41464 accepts both. So the misses reported here on 1.62 and 1.63 can reach the same wait. I have not verified that they do.

    The missing Page.frameNavigated looks like a Chromium bug. On Playwright's side, a fallback would unblock initialization when the main frame reports lifecycle events for a loader other than the one Page.getFrameTree returned, for example by reading the frame tree again.

    We work around it the way IF (@illusionfield) describes: a window bubble-phase listener records event.defaultPrevented after the page's own handlers and then cancels the default, so no tab opens.

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

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions