Isolating Profile Reuse, Headless Mode, and save_as()

Summary

  • A newly created profile succeeded in its first Edge process and reproduced the failure after reuse.
  • Headed and headless runs both reproduced the crash.
  • The failure remained when download.save_as() was replaced and when Playwright’s Download API was removed later in the investigation.
  • Simplifying launch arguments and changing download behavior did not remove the pipe-path failure.

Test method

The comparison rule was simple: change one condition, keep the remaining inputs fixed, and record both successful and failed runs.

The main candidates were:

  1. accumulated state in the original profile;
  2. behavior unique to headless mode;
  3. the Download.save_as() API;
  4. a specific download behavior or launch argument.

New profile reuse

The first crash had occurred with a profile that had been used previously. To remove that history, Edge was started with an empty, test-only user-data directory.

During the first Edge process, all five downloads completed:

new profile / launch 1 / download 1 -> success
new profile / launch 1 / download 2 -> success
new profile / launch 1 / download 3 -> success
new profile / launch 1 / download 4 -> success
new profile / launch 1 / download 5 -> success

After Edge closed normally, the same profile was used again:

same profile / launch 1 -> success
same profile / launch 2 or 3 -> native crash during download

The pattern appeared across multiple independently created profiles. That ruled against corruption limited to the original personal profile. Reuse across Edge processes remained an important condition.

Headed and headless execution

Headless browser execution can differ from a normal visible browser, so both modes were tested.

Mode Observed result
Headless First launch succeeded; a later launch disconnected during download
Headed First launch succeeded; a later launch disconnected during download

Crash dumps collected later from both modes pointed to the same instruction offset within msedge.dll. Headless mode was not required for the failure.

Removing save_as()

The original exception named Download.save_as(), making it a natural suspect. The download workflow was repeated with different completion paths.

Test Observed result
Save with Download.save_as() Edge exits; Playwright reports TargetClosedError
Read the temporary file through download.path() Same native crash
Send raw CDP commands without Playwright’s Download API Same native crash

The observed order explains why save_as() appeared in the error:

1. Edge encounters a native failure
2. The Edge process exits
3. The CDP connection closes
4. Playwright marks browser objects as closed
5. save_as() reports TargetClosedError

save_as() observed the closed process; it was not required to cause the crash.

Download behavior

Playwright 1.62.0 uses behavior similar to allowAndName when configuring downloads. A simpler allow behavior was also tested.

pipe + allow        -> reproduces
pipe + allowAndName -> reproduces
port + allowAndName -> successful in the observed runs

Filename handling alone did not explain the result.

Launch arguments

Playwright adds browser arguments for automation stability. Candidate arguments were removed in stages, including feature-disable lists and Edge-specific update or compatibility switches.

The crash remained with a reduced pipe launch. The launch on which it appeared sometimes shifted from the second to the third run, but the failure did not disappear. This does not prove that every argument is irrelevant; it shows that no single obvious default argument explained the observed pattern.

Results

Hypothesis Result Basis
Only the original profile was corrupt Not supported New profiles reproduced after reuse
Headless mode was required Excluded Headed mode also reproduced
save_as() was required Excluded The crash reproduced without the Download API
allowAndName alone caused the crash Not supported allow also reproduced over pipe
One obvious default launch argument caused it Not supported Reduced pipe launches still reproduced

The remaining question was whether Playwright itself was required. The next step replaced the automation library with direct CDP communication from Node.js standard modules.