microsoft / microsoft/playwright

feat(electron): add timeout option to electronApp.close() for force-kill escalation

Open
#40,586 0 comments 1 reaction 2 assignees View on GitHub

@pavelfeldman is already working on this.

Since May 4, 2026.

  • #40613 by @copilot-swe-agent — merged
feature-electron
Dominant language
TypeScript
Stars
96.3k
Forks
6.5k
Avg merge
1d 6h
Merged PRs (30d)
180

Description

Feature Request

Is your feature request related to a problem?

electronApp.close() currently waits indefinitely for the Electron process to exit. If the application has before-quit handlers that prevent shutdown, leaky IPC handlers, or child processes that keep it alive, close() hangs forever until the test-level timeout kills everything.

This is a well-documented pain point:

  • #12189 — app.quit() called but process sticks around
  • #27523 — Electron tests freeze 30s on close
  • #39248 — Leaky IPC handlers cause hangs on Linux/Windows
  • #13874 — Electron hangs closing context

PR #11336 rightfully removed the old hardcoded 30s timeout (which was inconsistent with other close() methods). But now there is no way for users to specify a grace period with force-kill escalation.

The force-kill infrastructure already exists in processLauncher.ts (killProcess() → SIGKILL / taskkill /T /F) but is only reachable via OS signals (SIGINT/SIGTERM), not through the public close() API.

Describe the solution

Add an opt-in timeout option to electronApp.close():

// Default: wait forever (unchanged behavior)
await electronApp.close();

// With timeout: force-kill if app does not exit within 10s
await electronApp.close({ timeout: 10_000 });

When specified:

  1. Initiate graceful shutdown (existing flow: browser.close()app.quit()worker._disconnect())
  2. Start a timer
  3. If the process exits within the timeout, clear the timer (no force-kill)
  4. If the timer fires first, call the existing kill() from processLauncher (SIGKILL/taskkill)
  5. Wait for the process to fully exit

This is different from the removed behavior (#11336) because:

  • Opt-in: no timeout by default (backward compatible)
  • Force-kills instead of throwing: the old timeout threw an error and left the process running
  • Uses existing infrastructure: wires the kill function from launchProcess into ElectronApplication

Real-world use case

We maintain E2E tests for a VS Code extension using Playwright Electron. Our test teardown was flaky because close() would occasionally hang when the Electron app did not exit cleanly. We had to build a 3-layer workaround: app.quit()app.close() wrapped in Promise.race with manual timer → SIGKILL fallback. This feature would replace that pattern with a single API call.

I have a PR ready if you are open to this.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.