Skip to content

fix(electron): close pages from the main process to avoid Target.closeTarget hang#41695

Merged
yury-s merged 4 commits into
microsoft:mainfrom
yury-s:test-electron-teardown-hang
Jul 10, 2026
Merged

fix(electron): close pages from the main process to avoid Target.closeTarget hang#41695
yury-s merged 4 commits into
microsoft:mainfrom
yury-s:test-electron-teardown-hang

Conversation

@yury-s

@yury-s yury-s commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

  • page.close() sends CDP Target.closeTarget and resolves only on the browser-level Target.detachedFromTarget event. On Electron, when the close races a navigation that is being committed, Electron acks Target.closeTarget with success but silently drops the actual close, so the target is never destroyed and page.close() hangs forever (Electron has no unload watchdog to recover, unlike Chrome). This surfaced as flaky Electron teardown timeouts in CI (e.g. page-autowaiting-no-hang.spec.ts "assigning location to about:blank").
  • Fix in the Electron layer: a per-page custom close handler closes the page from the main process via webContents.close(), which is not subject to the CDP/navigation race. webContents.close() (without waitForBeforeUnload) closes without running beforeunload, matching page.close(runBeforeUnload: false) force-close semantics.
  • Not an Electron version regression — reproduces equally on 42.4.1 / 42.5.0 / 42.6.1.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread tests/electron/electron-app.js Outdated
@yury-s
yury-s force-pushed the test-electron-teardown-hang branch from 13d604f to 7df6703 Compare July 9, 2026 22:43
@yury-s yury-s changed the title test(electron): fix teardown hang when closing windows fix(electron): close pages from the main process to avoid Target.closeTarget hang Jul 9, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@yury-s
yury-s requested a review from dgozman July 10, 2026 00:59
…eTarget hang

page.close() sends CDP Target.closeTarget and waits for the browser-level
Target.detachedFromTarget event to resolve. On Electron, when the close
races a navigation that is being committed, Electron acks Target.closeTarget
with success but silently drops the actual close, so the target is never
destroyed and page.close() hangs forever (Electron has no unload watchdog to
recover, unlike Chrome).

Close the page from the main process instead via a per-page custom close
handler that calls webContents.close(). This is not subject to the CDP/
navigation race, and webContents.close() (without waitForBeforeUnload) closes
without running beforeunload, matching page.close(runBeforeUnload: false)
semantics.
@yury-s
yury-s force-pushed the test-electron-teardown-hang branch from 7df6703 to f17a470 Compare July 10, 2026 01:00
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread packages/playwright-core/src/server/electron/electron.ts Outdated
Comment thread packages/playwright-core/src/server/electron/electron.ts Outdated
Comment thread packages/playwright-core/src/server/electron/electron.ts Outdated
- PLAYWRIGHT_ELECTRON_LEGACY_PAGE_CLOSE opts back into Target.closeTarget.
- Fall back to the default close when the webContents cannot be found.
- Clarify why we close the webContents rather than the BrowserWindow.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@yury-s yury-s added the CQ1 label Jul 10, 2026
page.close({ runBeforeUnload: true }) went through the CDP Page.close path,
which has the same problem as Target.closeTarget on Electron: it leaves a
window with a pending navigation open. Pass runBeforeUnload to the custom
close handler and map it to webContents.close({ waitForBeforeUnload }), so
both close variants close from the main process.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@yury-s yury-s added CQ1 and removed CQ1 labels Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

3 failed
❌ [chrome] › mcp/annotate.spec.ts:497 › should disengage annotate mode when --annotate client disconnects @mcp-macos-latest-chrome
❌ [chromium] › mcp/dashboard.spec.ts:125 › daemon show: closing page exits the process @mcp-macos-latest-chromium
❌ [firefox] › mcp/http.spec.ts:104 › http transport browser lifecycle (isolated) @mcp-ubuntu-latest-firefox

7757 passed, 1249 skipped


Merge workflow run.

@yury-s
yury-s merged commit 0f92543 into microsoft:main Jul 10, 2026
71 of 76 checks passed
@yury-s
yury-s deleted the test-electron-teardown-hang branch July 10, 2026 22:40
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

8 flaky ⚠️ [chromium-library] › library/beforeunload.spec.ts:130 › should support dismissing the dialog multiple times `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:337 › screencast › should work for popups `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/popup.spec.ts:260 › should not throw when click closes popup `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:717 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:177 › serviceWorker(), and fromServiceWorker() work `@chromium-ubuntu-22.04-node22`
⚠️ [chromium-library] › library/video.spec.ts:476 › screencast › should capture static page in persistent context @smoke `@chromium-ubuntu-22.04-node22`
⚠️ [chromium-library] › library/video.spec.ts:717 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node22`
⚠️ [firefox-library] › library/heap.spec.ts:223 › should not leak workers `@firefox-ubuntu-22.04-node20`

49516 passed, 1161 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 2"

1 failed
❌ [chromium-library] › library/screenshot.spec.ts:213 › page screenshot › should not hang when event loop is blocked @driver

32 flaky ⚠️ [chromium-library] › library/chromium/chromium.spec.ts:299 › should report intercepted service worker requests in HAR `@tracing-chromium`
⚠️ [chromium-page] › page/page-screenshot.spec.ts:453 › page screenshot › should take fullPage screenshots during navigation `@tracing-chromium`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@msedge-dev-windows-latest`
⚠️ [chromium-library] › library/beforeunload.spec.ts:130 › should support dismissing the dialog multiple times `@chrome-ubuntu-22.04`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@chrome-ubuntu-22.04`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@msedge-windows-latest`
⚠️ [chromium-library] › library/video.spec.ts:717 › screencast › should work with video+trace `@chromium-windows-latest`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:434 › should produce network events, routing, and annotations for Service Worker (advanced) `@chromium-macos-15-large`
⚠️ [chromium-library] › library/heap.spec.ts:304 › cycle handles `@chromium-macos-15-large`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:177 › serviceWorker(), and fromServiceWorker() work `@chromium-macos-14-xlarge`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:371 › should produce network events, routing, and annotations for Service Worker `@chromium-macos-14-xlarge`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:434 › should produce network events, routing, and annotations for Service Worker (advanced) `@chromium-macos-14-xlarge`
⚠️ [chromium-library] › library/video.spec.ts:717 › screencast › should work with video+trace `@chromium-macos-15-xlarge`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@chrome-windows-latest`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@chrome-beta-ubuntu-22.04`
⚠️ [chromium-library] › library/browsertype-connect.spec.ts:446 › launchServer › should reject waitForEvent before browser.close finishes `@chrome-macos-latest`
⚠️ [chromium-library] › library/browsertype-connect.spec.ts:647 › launchServer › should properly disconnect when connection closes from the client side `@chrome-macos-latest`
⚠️ [chromium-library] › library/chromium/chromium.spec.ts:434 › should produce network events, routing, and annotations for Service Worker (advanced) `@chrome-macos-latest`
⚠️ [chromium-library] › library/video.spec.ts:456 › screencast › should be 800x600 with null viewport `@chrome-macos-latest`
⚠️ [firefox-library] › library/heap.spec.ts:223 › should not leak workers `@tracing-firefox`
⚠️ [firefox-library] › library/defaultbrowsercontext-2.spec.ts:111 › should restore state from userDataDir `@firefox-macos-15-large`
⚠️ [firefox-library] › library/inspector/cli-codegen-java.spec.ts:24 › should print the correct imports and context options `@firefox-macos-15-large`
⚠️ [firefox-library] › library/inspector/cli-codegen-javascript.spec.ts:24 › should print the correct imports and context options `@firefox-macos-15-large`
⚠️ [firefox-library] › library/proxy.spec.ts:316 › should use socks proxy in second page `@firefox-macos-15-large`
⚠️ [firefox-library] › library/beforeunload.spec.ts:20 › should close browser with beforeunload page `@firefox-windows-latest`
⚠️ [firefox-library] › library/firefox/launcher.spec.ts:48 › should support custom firefox policies `@firefox-windows-latest`
⚠️ [firefox-library] › library/inspector/cli-codegen-python-async.spec.ts:37 › should print the correct context options for custom settings `@firefox-windows-latest`
⚠️ [firefox-library] › library/proxy.spec.ts:144 › should authenticate `@firefox-windows-latest`
⚠️ [webkit-library] › library/playwright-client.spec.ts:26 › should connect from a page and drive the same browser `@tracing-webkit`
⚠️ [webkit-library] › library/inspector/cli-codegen-2.spec.ts:526 › cli codegen › should generate getByTestId for any of the configured testIdAttributes `@webkit-macos-15-large`
⚠️ [webkit-library] › library/tracing.spec.ts:429 › should produce screencast frames scale `@webkit-macos-15-large`
⚠️ [webkit-page] › page/page-request-fallback.spec.ts:221 › post data › should amend post data `@webkit-macos-15-large`

105882 passed, 4474 skipped


Merge workflow run.

@gofr

gofr commented Jul 20, 2026

Copy link
Copy Markdown

@yury-s I haven't managed to create a minimal reproduction yet (so haven't filed a bug yet), but I'm running into this on Chromium too. According to my testing the race also exists in Chromium and was introduced there before the version that Electron 42.4.1 is based on.

I think this corresponds to https://issues.chromium.org/issues/536385539.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants