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]: middle / modifier click intermittently fails to open a new tab on Playwright 1.62.x #42142
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.
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.
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>');});awaitpage.goto(server.PREFIX+'/');constpopupPromise=context.waitForEvent('page');awaitpage.getByRole('link',{name: 'Open target'}).click({button: 'middle'});await(awaitpopupPromise).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.
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.
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.
Additional independent data point for the stranded-tab / missing page event symptom described above. We are not claiming a confirmed root cause or that every failure in this issue has the same cause.
Versions and environment
playwright and playwright-core1.62.1, Chromium headless shell 151.0.7922.34, revision 1234, default chromium.launch({ headless: true }) in the failing CI run.
Failing run: GitHub-hosted Ubuntu 24.04, Linux x86_64, Node 22.23.3, npm 10.9.9. The Node script runs scenarios sequentially, without Playwright Test workers or retries; preceding scenarios had already run in the same browser.
Comparison: five fresh local Node processes passed on Linux ARM64, using the same SDK versions and headless-shell revision via an explicit executablePath, with CPU quota 1 and memory limit 1 GiB. The first CI iteration failed; we stopped there. Different architecture/resource/timing conditions, so this is not a controlled local-vs-CI comparison or a statistical failure rate.
We have not tested the latest release for this report. No version boundary, regression range, upgrade fix, or relation to the Chromium CL mentioned earlier is claimed.
Observed API sequence (excerpt of the existing application test, not a standalone minimal reproduction):
// A same-origin dashboard with a stretched row link, after several normal// in-page navigation/back cycles. The destination is fulfilled by context.route.awaitcontext.route(destination,route=>route.fulfill({contentType: 'text/html',body: '<!doctype html><title>Target</title>'}));constopened=context.waitForEvent('page');opened.catch(()=>{});// preserve the original await while observing errorsawaitpage.locator(rowHeaderSelector).first().click({force: true,modifiers: ['Control']});constpopup=awaitopened;// timed out after 30 sawaitpopup.waitForLoadState();
rowHeaderSelector and destination are application-specific variables; no claim that this excerpt alone reproduces the failure. We are adding diagnostic evidence to this existing issue with a public MRE, rather than filing a new bug without a validated standalone reproduction.
What the failing trace establishes
Control-down and mouse moved/pressed/released carried modifiers: 2; all input commands were ACKed. Control-up followed them.
A second Target.attachedToTarget, type page, arrived in the same browser context, initially waitingForDebugger: true.
Its Page.enable, Page.setLifecycleEventsEnabled, Runtime.enable, Target.setAutoAttach, and Runtime.runIfWaitingForDebugger commands were ACKed without errors. DOMContentLoaded, load, and frameStoppedLoading arrived on its session.
A post-timeout, protected browser.newBrowserCDPSession() / Target.getTargets check confirmed both targets still attached, the opener at its dashboard URL and the second target at the exact expected destination. That check was not installed before the click.
The same context still exposed only the opener via context.pages(). A Node-side context.on('page') listener had recorded zero new-page events.
Across the received protocol messages on the connection, there was no Page.frameNavigated for the popup and no lifecycle init during the destination load. The trace ring had 0 dropped records and 0 parse failures. In all five successful local captures, init and frameNavigated preceded the Node page event (5–10 ms after target attachment, or 6–11 ms after the final mouse-input send).
Capture limitations
The capture was a temporary Node-side hook of the SDK's existing protocol logger, with no listeners, observers, or injected code added to the page. It retained only a whitelist of input/target/page lifecycle metadata and ACKs; raw network, headers, cookies, evaluation payloads, and document content were discarded. Enabling protocol serialization and parsing still changes Node-side timing, so successful runs are only non-reproductions. The initial empty target URL is represented below as [invalid-url]; that is our sanitizer label for an empty string, not a Chromium error. The early lifecycle group at browser timestamp 541.569 (only its DOMContentLoaded is shown) also appears in successful runs; the later 541.612 DOMContentLoaded/load follows debugger release. Loader IDs were not retained, so those groups alone do not identify a particular new loader.
Our whitelist did not retain Page.getFrameTree or its response, or all other initialization-command ACKs. In the 1.62.1 SDK, page initialization waits for _firstNonInitialNavigationCommittedPromise, resolved either when the initial frame-tree URL is neither empty nor ":", or when a non-initial frameNavigated arrives. The trace is compatible with that wait remaining pending, but another initialization promise could also be pending. We cannot distinguish those possibilities or explain why the commit events are missing from this capture. This is a localization of the failure, not proof of the precise blocking await or upstream cause.
No private application URL, repository link, filesystem path, original target/context/session identifier, credential, or full raw protocol log is included. We have not used target polling/forced closure, DOM default-action cancellation, a different browser channel, or another acceptance mechanism as a replacement for the original test.
Activity
Mahnoor-Zaffar commentedon Aug 6, 2026
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.tsfile 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.
github-actions commentedon Aug 6, 2026
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:
--repeat-each=25 --workers=16on a 4-core box (heavy oversubscription): 2/50 failed withwaitForURL: Test timeout of 30000ms exceeded. Matches your report.<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 shipschromium-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@nexta spin against your real app would be a useful confirmation from your side.Triaged by the Playwright bot - agent run
YuriiMotov commentedon Aug 6, 2026
I've just tested it with
1.63.0-alpha-2026-08-06and it still fails:npx envinfo --preset playwright
github-actions commentedon Aug 6, 2026
Hi, I'm the Playwright bot and I took another look — thanks for coming back with the
@nextnumbers.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-alphawithchromium-1237.What I ran (your MRE, unchanged, on a 4-core box — heavy oversubscription at 16 workers)
--repeat-each=50)--repeat-each=25)@next)--repeat-each=50)Failure is the one you reported —
browserContext.waitForEvent('page')/waitForURLhangs 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=16Condensed self-contained repro
Same trigger as your MRE, boiled down to our fixtures (still needs load/parallelism to surface — one run passes):
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
alirezaedalat commentedon Aug 6, 2026
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.
Skn0tt commentedon Aug 7, 2026
My agent bisected this to https://chromium-review.googlesource.com/c/chromium/src/+/7987972, i've filed this against Chromium: https://issues.chromium.org/issues/543471700
daddyman commentedon Sep 1, 2026
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 shellSince then, we have not had a new tab failure for over a month.
6 remaining items
FGnosis commentedon Oct 14, 2026
Additional independent data point for the stranded-tab / missing
pageevent symptom described above. We are not claiming a confirmed root cause or that every failure in this issue has the same cause.Versions and environment
playwrightandplaywright-core1.62.1, Chromium headless shell 151.0.7922.34, revision 1234, defaultchromium.launch({ headless: true })in the failing CI run.executablePath, with CPU quota 1 and memory limit 1 GiB. The first CI iteration failed; we stopped there. Different architecture/resource/timing conditions, so this is not a controlled local-vs-CI comparison or a statistical failure rate.Observed API sequence (excerpt of the existing application test, not a standalone minimal reproduction):
rowHeaderSelectoranddestinationare application-specific variables; no claim that this excerpt alone reproduces the failure. We are adding diagnostic evidence to this existing issue with a public MRE, rather than filing a new bug without a validated standalone reproduction.What the failing trace establishes
modifiers: 2; all input commands were ACKed. Control-up followed them.Target.attachedToTarget, typepage, arrived in the same browser context, initiallywaitingForDebugger: true.Page.enable,Page.setLifecycleEventsEnabled,Runtime.enable,Target.setAutoAttach, andRuntime.runIfWaitingForDebuggercommands were ACKed without errors.DOMContentLoaded,load, andframeStoppedLoadingarrived on its session.browser.newBrowserCDPSession()/Target.getTargetscheck confirmed both targets still attached, the opener at its dashboard URL and the second target at the exact expected destination. That check was not installed before the click.contextstill exposed only the opener viacontext.pages(). A Node-sidecontext.on('page')listener had recorded zero new-page events.Page.frameNavigatedfor the popup and no lifecycleinitduring the destination load. The trace ring had 0 dropped records and 0 parse failures. In all five successful local captures,initandframeNavigatedpreceded the Nodepageevent (5–10 ms after target attachment, or 6–11 ms after the final mouse-input send).Capture limitations
The capture was a temporary Node-side hook of the SDK's existing protocol logger, with no listeners, observers, or injected code added to the page. It retained only a whitelist of input/target/page lifecycle metadata and ACKs; raw network, headers, cookies, evaluation payloads, and document content were discarded. Enabling protocol serialization and parsing still changes Node-side timing, so successful runs are only non-reproductions. The initial empty target URL is represented below as
[invalid-url]; that is our sanitizer label for an empty string, not a Chromium error. The early lifecycle group at browser timestamp 541.569 (only itsDOMContentLoadedis shown) also appears in successful runs; the later 541.612DOMContentLoaded/loadfollows debugger release. Loader IDs were not retained, so those groups alone do not identify a particular new loader.Our whitelist did not retain
Page.getFrameTreeor its response, or all other initialization-command ACKs. In the 1.62.1 SDK, page initialization waits for_firstNonInitialNavigationCommittedPromise, resolved either when the initial frame-tree URL is neither empty nor":", or when a non-initialframeNavigatedarrives. The trace is compatible with that wait remaining pending, but another initialization promise could also be pending. We cannot distinguish those possibilities or explain why the commit events are missing from this capture. This is a localization of the failure, not proof of the precise blocking await or upstream cause.Sanitized failing trace (relative times; URLs and opaque IDs replaced consistently)
{ "note": "Independent failing occurrence; opaque IDs and URLs consistently replaced; times relative to Control-down; omitted events are not represented as absent. The earlier 5 successful local traces are not pasted here.", "events": [ { "relativeMs": 0, "kind": "cdp", "direction": "send", "sessionId": "opener-session", "method": "Input.dispatchKeyEvent", "params": { "modifiers": 2, "autoRepeat": false, "isKeypad": false, "type": "rawKeyDown", "key": "Control", "code": "ControlLeft", "windowsVirtualKeyCode": 17 } }, { "relativeMs": 0, "kind": "ack", "sessionId": "opener-session", "method": "Input.dispatchKeyEvent", "result": {} }, { "relativeMs": 0, "kind": "cdp", "direction": "send", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "params": { "modifiers": 2, "x": 559.37, "y": 261.25, "buttons": 0, "type": "mouseMoved", "button": "none" } }, { "relativeMs": 0, "kind": "cdp", "direction": "send", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "params": { "modifiers": 2, "x": 559.37, "y": 261.25, "buttons": 1, "clickCount": 1, "type": "mousePressed", "button": "left" } }, { "relativeMs": 1, "kind": "cdp", "direction": "send", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "params": { "modifiers": 2, "x": 559.37, "y": 261.25, "buttons": 0, "clickCount": 1, "type": "mouseReleased", "button": "left" } }, { "relativeMs": 1, "kind": "ack", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "result": {} }, { "relativeMs": 2, "kind": "ack", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "result": {} }, { "relativeMs": 3, "kind": "ack", "sessionId": "opener-session", "method": "Input.dispatchMouseEvent", "result": {} }, { "relativeMs": 3, "kind": "cdp", "direction": "send", "sessionId": "opener-session", "method": "Input.dispatchKeyEvent", "params": { "modifiers": 0, "type": "keyUp", "key": "Control", "code": "ControlLeft", "windowsVirtualKeyCode": 17 } }, { "relativeMs": 4, "kind": "cdp", "direction": "recv", "method": "Target.attachedToTarget", "params": { "sessionId": "popup-session", "waitingForDebugger": true, "targetInfo": { "targetId": "popup-target", "browserContextId": "context-A", "attached": true, "canAccessOpener": false, "type": "page", "url": "[invalid-url]" } } }, { "relativeMs": 11, "kind": "ack", "sessionId": "opener-session", "method": "Input.dispatchKeyEvent", "result": {} }, { "relativeMs": 11, "kind": "cdp", "direction": "send", "sessionId": "popup-session", "method": "Page.enable", "params": {} }, { "relativeMs": 11, "kind": "cdp", "direction": "send", "sessionId": "popup-session", "method": "Page.setLifecycleEventsEnabled", "params": { "enabled": true } }, { "relativeMs": 11, "kind": "cdp", "direction": "send", "sessionId": "popup-session", "method": "Runtime.enable", "params": {} }, { "relativeMs": 12, "kind": "cdp", "direction": "send", "sessionId": "popup-session", "method": "Target.setAutoAttach", "params": { "flatten": true, "autoAttach": true, "waitForDebuggerOnStart": true } }, { "relativeMs": 12, "kind": "cdp", "direction": "send", "sessionId": "popup-session", "method": "Runtime.runIfWaitingForDebugger", "params": {} }, { "relativeMs": 24, "kind": "ack", "sessionId": "popup-session", "method": "Target.setAutoAttach", "result": {} }, { "relativeMs": 31, "kind": "ack", "sessionId": "popup-session", "method": "Page.enable", "result": {} }, { "relativeMs": 31, "kind": "cdp", "direction": "recv", "sessionId": "popup-session", "method": "Page.lifecycleEvent", "params": { "frameId": "popup-target", "timestamp": 541.569261, "name": "DOMContentLoaded" } }, { "relativeMs": 31, "kind": "ack", "sessionId": "popup-session", "method": "Page.setLifecycleEventsEnabled", "result": {} }, { "relativeMs": 32, "kind": "ack", "sessionId": "popup-session", "method": "Runtime.enable", "result": {} }, { "relativeMs": 50, "kind": "ack", "sessionId": "popup-session", "method": "Runtime.runIfWaitingForDebugger", "result": {} }, { "relativeMs": 54, "kind": "cdp", "direction": "recv", "sessionId": "popup-session", "method": "Page.lifecycleEvent", "params": { "frameId": "popup-target", "timestamp": 541.612229, "name": "DOMContentLoaded" } }, { "relativeMs": 55, "kind": "cdp", "direction": "recv", "sessionId": "popup-session", "method": "Page.lifecycleEvent", "params": { "frameId": "popup-target", "timestamp": 541.612352, "name": "load" } }, { "relativeMs": 55, "kind": "cdp", "direction": "recv", "sessionId": "popup-session", "method": "Page.frameStoppedLoading", "params": { "frameId": "popup-target" } } ], "afterTimeout": { "targets": [ { "type": "page", "targetId": "opener-target", "browserContextId": "context-A", "attached": true, "url": "https://example.test/dashboard/" }, { "type": "page", "targetId": "popup-target", "browserContextId": "context-A", "attached": true, "url": "https://example.test/target/" } ], "contextPages": [ "https://example.test/dashboard/" ], "nodePageEvents": 0, "traceDropped": 0, "traceParseFailures": 0, "missingForPopup": [ "Page.frameNavigated", "Page.lifecycleEvent name=init" ] } }No private application URL, repository link, filesystem path, original target/context/session identifier, credential, or full raw protocol log is included. We have not used target polling/forced closure, DOM default-action cancellation, a different browser channel, or another acceptance mechanism as a replacement for the original test.
test(browser): use full Chromium fixes #150
Fix production acceptance readiness and vulnerable build dependencies (…
fix: keep Playwright 1.62.1 so new-tab tests open their tab
fix: update eMudhra mitigations and Prek (#1487)
dgozman commentedon Oct 27, 2026
🛑 Blocked on upstream issue https://issues.chromium.org/issues/543471700
pavelfeldman commentedon Nov 2, 2026
This is fixed upstream (Chromium) on Oct 27th. Will likely make it only into v1.66.