Skip to content

fix(chromium): prevent TargetClosedException on main-frame cross-process navigation - #41424

Closed
Karim Nabli (karimnabli) wants to merge 2 commits into
microsoft:mainfrom
karimnabli:fix/41348-chromium-cross-origin-race
Closed

fix(chromium): prevent TargetClosedException on main-frame cross-process navigation#41424
Karim Nabli (karimnabli) wants to merge 2 commits into
microsoft:mainfrom
karimnabli:fix/41348-chromium-cross-origin-race

Conversation

@karimnabli

Copy link
Copy Markdown

Fixes#41348

Root Cause

In crPage.ts, the _onDetachedFromTarget method assumes that a detached target implies either a swap or a true detach. However, Azure OAuth introduces a third case: a main-frame cross-process navigation.

Due to the microtask delay in Page.enable, the session mapping changes, and Playwright incorrectly calls frameDetached, throwing a TargetClosedException immediately after the "Stay signed in?" click.

Fix

Added a guard in _onDetachedFromTarget to verify if the session currently mapped to the targetId is still the childFrameSession that triggered the detachment. If a new session has taken over (indicating a cross-process navigation), we skip the frameDetached call and simply dispose of the old session.

Test

Added a regression test in tests/library/chromium/oopif.spec.ts that simulates a cross-origin redirect after a form submission to ensure the page survives the transition.

…ess navigation (microsoft#41348)
Signed-off-by: Karim Nabli <karim.nabli.ie@gmail.com>
@karimnabli

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@karimnabli

Karim Nabli (karimnabli) commented Jun 23, 2026

Copy link
Copy Markdown
Author

Pavel Feldman (@pavelfeldman)Dmitry Gozman (@dgozman)

I’ve implemented a fix for #41348

Root cause:
In crPage.ts, _onDetachedFromTarget assumes a detached target is either a swap or a true detach. However, Azure OAuth introduces a third case: a main-frame cross-process navigation. Due to the microtask delay in Page.enable, the session mapping changes and Playwright incorrectly calls frameDetached, leading to a TargetClosedException right after the "Stay signed in?" click.

Fix:
Added a guard in _onDetachedFromTarget to verify whether the session currently mapped to the targetId is still the childFrameSession that triggered the detachment. If a new session has taken over (cross-process navigation), the frameDetached call is skipped and the old session is simply disposed.

Test:
Added a regression test in tests/library/chromium/oopif.spec.ts that simulates a cross-origin redirect after form submission to ensure the page survives the transition.

Could you please review whether this approach aligns with the expected handling of cross-process navigations?

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

3 failed
❌ [chrome] › mcp/http.spec.ts:349 › client should receive list roots request @mcp-ubuntu-latest-chrome
❌ [chromium] › mcp/http.spec.ts:349 › client should receive list roots request @mcp-macos-latest-chromium
❌ [firefox] › mcp/http.spec.ts:349 › client should receive list roots request @mcp-macos-latest-firefox

7458 passed, 1132 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

2 failed
❌ [chromium-library] › library/browsercontext-add-init-script.spec.ts:28 › should work without navigation, after all bindings @chromium-ubuntu-22.04-arm-node20
❌ [chromium-library] › library/browsercontext-expose-function.spec.ts:77 › should be callable from-inside addInitScript @chromium-ubuntu-22.04-arm-node20

7 flaky⚠️ [chromium-library] › library/chromium/chromium.spec.ts:177 › serviceWorker(), and fromServiceWorker() work `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:177 › serviceWorker(), and fromServiceWorker() work `@chromium-ubuntu-22.04-node20`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:211 › should intercept service worker requests (main and within) `@chromium-ubuntu-22.04-node20`
⚠️ [chromium-library] › library/tracing.spec.ts:430 › should produce screencast frames crop `@chromium-ubuntu-22.04-node22`
⚠️ [chromium-library] › library/video.spec.ts:680 › screencast › should capture full viewport on hidpi `@chromium-ubuntu-22.04-node22`
⚠️ [firefox-library] › library/trace-viewer.spec.ts:1630 › should highlight locator in iframe while typing `@firefox-ubuntu-22.04-node20`
⚠️ [firefox-page] › page/page-emulate-media.spec.ts:144 › should keep reduced motion and color emulation after reload `@firefox-ubuntu-22.04-node20`

49170 passed, 1163 skipped


Merge workflow run.

@karimnabli

Karim Nabli (karimnabli) commented Jul 2, 2026

Copy link
Copy Markdown
Author

Hi, Pavel Feldman (@pavelfeldman)Dmitry Gozman (@dgozman)
Does these MCP
MCP / macos-latest - chromium (pull_request)
MCP / macos-latest - chromium (pull_request)Failing after 18m
MCP / macos-latest - firefox (pull_request)
MCP / macos-latest - firefox (pull_request)Failing after 28m
MCP / ubuntu-latest - chrome (pull_request)
MCP / ubuntu-latest - chrome (pull_request)Failing after 14m
tests 1 / ubuntu-22.04-arm (chromium - Node.js 20) (pull_request)
tests 1 / ubuntu-22.04-arm (chromium - Node.js 20) (pull_request)Failing after 18m
Are failing because of the changes in this PR ?

@karimnabli

Karim Nabli (karimnabli) commented Jul 8, 2026

Copy link
Copy Markdown
Author

Hi, Pavel Feldman (@pavelfeldman)Dmitry Gozman (@dgozman)Simon Knott (@Skn0tt) – I wanted to flag that the failing MCP test (tests/mcp/http.spec.ts:349 - "client should receive list roots request") appears to be a pre-existing flakiness issue unrelated to this PR.

Evidence:

My PR changes:

  • Fixes a race condition in _onDetachedFromTarget where cross-process navigation was incorrectly triggering TargetClosedException
  • Adds a guard to verify if the current session mapping has changed before calling frameDetached
  • Includes a regression test for the specific cross-origin redirect scenario

The MCP job failure should not block this PR. If needed, I'm happy to see if this test flakiness can be addressed in a separate issue.

Comment threadtests/library/chromium/oopif.spec.ts
Comment threadpackages/playwright-core/src/server/chromium/crPage.ts
Signed-off-by: Karim Nabli <karim.nabli.ie@gmail.com>
@karimnabli
Karim Nabli (karimnabli)force-pushed the fix/41348-chromium-cross-origin-race branch from 1c03e8f to 8a6a13dCompareJuly 21, 2026 20:32
@dgozman

Copy link
Copy Markdown
Collaborator

I tried to run pretty much your exact test on the bots, without a fix, to see what's going wrong: #41930. Turns out, it passes on every bot. Now I am not sure we are fixing the right problem at all. We need some easy-ish repro that we can turn into a test to make sure we are fixing the actual issue.

@karimnabli

Copy link
Copy Markdown
Author

Dmitry Gozman (@dgozman)
You are absolutely right about the importance of a failing test.
I have spent significant time trying to force this race condition in the TypeScript test harness, but I have not been able to make it fail reliably.
To be thorough, I attempted several approaches to widen the race window:

  • Standard server.setRoute with artificial network delays on the redirect.
  • A multi-step cross-origin form submission chain mimicking the exact Azure OAuth flow.
  • A raw Node.js net TCP server (bypassing Node's http module entirely) to exactly replicate the Connection: close and immediate socket teardown behavior of the C# TcpListener reproduction.

Despite these exhaustive efforts, the test passes locally even without the fix. The conclusion is that the Playwright test harness environment (due to Node.js event loop timing and OS-level TCP stack differences) fundamentally smooths over this specific microtask race window.

However, the C# reproduction in the issue description #41348 reliably triggers the TargetClosedException in Playwright 1.60.0 and succeeds with this fix. That script serves as definitive proof that the bug exists and the fix resolves it.

I will not make further code changes to the test. The fix itself is a defensive guard that prevents dispose() from unconditionally clobbering the _sessions map if a cross-process navigation has already taken over the targetId. This is objectively safer code, regardless of the test harness's ability to reproduce the timing.

I understand if this is not sufficient for merging without a failing test. Unless you have a specific insight on how to force this exact CDP timing window within the Playwright test environment, I believe the defensive nature of the fix and the verified real-world reproduction could be considered as sufficient.

@pavelfeldman

Copy link
Copy Markdown
Member

I'll close this as per #41424 (comment)

@karimnabli

Karim Nabli (karimnabli) commented Aug 1, 2026

Copy link
Copy Markdown
Author

Pavel Feldman (@pavelfeldman)Dmitry Gozman (@dgozman)
I understand the PR was closed per the project's policy regarding failing tests in the harness. However, I need to highlight the real-world impact of this open issue: this bug is actively breaking our production automation tests.

We have a critical Azure OAuth login flow that:

  • Works 100% of the time in manual browser testing.

  • Fails 100% of the time in Playwright 1.60.0 automated tests with TargetClosedException.

  • Works 100% of the time when the defensive guard (from the closed PR) is applied.

This is not a theoretical edge case. It is actively blocking our CI/CD pipeline and preventing us from running automated regression tests for our authentication flow.

The proposed fix was a simple, defensive guard that prevents dispose() from unconditionally clobbering the _sessions map if a cross-process navigation has already taken over the targetId. It is objectively safer code that cannot break existing functionality.

Since the PR was closed due to the test harness's inability to reproduce this specific microtask race window (despite exhaustive attempts with raw TCP servers and artificial delays), I am asking for guidance on how to proceed:

  1. Can the defensive fix be reconsidered given it is actively blocking real-world CI/CD pipelines and the C# reproduction reliably proves the bug?

  2. Is there a specific, documented way to force this exact CDP timing window within the Playwright test environment that I missed?

  3. Is there an alternative workaround you recommend for this specific OAuth redirect scenario?
    We need a path forward because our automation is currently blocked. The C# reproduction script in my previous comments remains the most reliable way to verify this behavior.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: TargetClosedException thrown after clicking "Yes" on MS OAuth prompt in 1.60 (Regression from 1.58)

4 participants

@karimnabli@dgozman@pavelfeldman@dcrousso