Task
Make npm run test:e2e start reliably on a developer machine again. Since the Astro 7 upgrade (#176), the first local run frequently dies with Error: Process from config.webServer exited early. before a single test executes. Astro 7 added a dev-server lock: when a server is already listening on the target port, astro dev prints Dev server already running at http://localhost:4321 (pid N) and exits 0 instead of starting or falling through to another port. Playwright's webServer treats that immediate exit as fatal. After this is fixed, npm run test:e2e should pass from a cold start regardless of whether another Astro dev server is already running on the machine.
Context
Found while verifying the Astro 6 → 7 bump before merging #176. CI is not affected — playwright.config.ts:31 branches on process.env.CI and uses npm run build && npm run preview, which has no dev-server lock. Production is unaffected too (static build). This is local-DX only, but it matters more here than usual for two reasons:
CLAUDE.md lists npm run test:e2e as a gate to run before claiming work complete. A future agent that hits this will read it as a real test failure and start debugging the wrong thing.
- This repo is routinely worked in multiple git worktrees at once, each with its own
astro dev on the same default port 4321. During this investigation the port was held by a dev server belonging to an unrelated worktree (link-spotify-podcasts-5dfcea), which is precisely the condition that triggers the early exit. Astro 6 tolerated this; Astro 7 does not.
Reproduction
- Have any Astro dev server running on port 4321 — commonly one from a different worktree of this repo.
- In a second worktree, run
npm run test:e2e.
Expected: Playwright starts or reuses a server and runs the suite.
Actual: Error: Process from config.webServer exited early. — zero tests run. A second invocation often succeeds, because by then a server is listening at the URL and reuseExistingServer short-circuits before the command is spawned. That intermittency is what makes this worth fixing rather than documenting.
Environment: astro 7.1.1, @playwright/test 1.61.0, macOS (darwin 25.5.0), local only (CI unset).
Pointers
- playwright.config.ts:26-36 — the
webServer block. command at :31, url at :32, reuseExistingServer: !process.env.CI at :33.
- package.json:11 —
"dev": "astro dev", the command that now early-exits.
- package.json:14 —
"preview": "astro preview", the CI-side path that does not have this problem.
npx astro dev --help — documents the Astro 7 surface added here: subcommands stop / status / logs, and the flags --background, --port, and --ignore-lock ("Start the dev server even if another one is already running, without checking or writing the lock file").
Suggested directions
Pick one; they are listed roughly easiest-to-most-robust. Do not do all of them.
- Add
--ignore-lock to the dev command used by Playwright. Smallest change, but two servers then race for the same port.
- Give the e2e run its own port (e.g.
astro dev --port 4331) and update webServer.url to match, so parallel worktrees stop colliding. Note url and the port must stay in sync.
- Use the CI path locally too (
npm run build && npm run preview). Most faithful to what CI verifies; costs a build on every local e2e run, and the comment at playwright.config.ts:27-30 explains why astro dev was chosen locally in the first place — preserve that reasoning if you change it.
Constraints
- Do not change the CI branch of
webServer.command — CI is green today and astro preview avoids the Vite dep-optimizer race described at playwright.config.ts:27-30.
- Keep the comment block at playwright.config.ts:27-30 meaningful; if the local path changes, update the comment rather than deleting the rationale.
- No new dependencies.
- Must not require the developer to manually stop a server first — "run
astro dev stop before testing" is a workaround, not a fix.
Acceptance criteria
Out of scope
- The pre-existing iframe-embed e2e test that fails locally but passes in CI — a known local-only false alarm, unrelated to this.
- Any change to
astro.config.mjs optimizeDeps tuning.
- Reworking how many ports the project uses generally, or introducing per-worktree port assignment as a broader convention.
Reasoning guidance
Reproduce the failure first — start a dev server on 4321, then run the suite in a second checkout and confirm you see webServer exited early before changing anything. The failure is order-dependent and a fix that is not verified against an already-occupied port will look like it works on the second run for the wrong reason.
Task
Make
npm run test:e2estart reliably on a developer machine again. Since the Astro 7 upgrade (#176), the first local run frequently dies withError: Process from config.webServer exited early.before a single test executes. Astro 7 added a dev-server lock: when a server is already listening on the target port,astro devprintsDev server already running at http://localhost:4321 (pid N)and exits 0 instead of starting or falling through to another port. Playwright'swebServertreats that immediate exit as fatal. After this is fixed,npm run test:e2eshould pass from a cold start regardless of whether another Astro dev server is already running on the machine.Context
Found while verifying the Astro 6 → 7 bump before merging #176. CI is not affected —
playwright.config.ts:31branches onprocess.env.CIand usesnpm run build && npm run preview, which has no dev-server lock. Production is unaffected too (static build). This is local-DX only, but it matters more here than usual for two reasons:CLAUDE.mdlistsnpm run test:e2eas a gate to run before claiming work complete. A future agent that hits this will read it as a real test failure and start debugging the wrong thing.astro devon the same default port 4321. During this investigation the port was held by a dev server belonging to an unrelated worktree (link-spotify-podcasts-5dfcea), which is precisely the condition that triggers the early exit. Astro 6 tolerated this; Astro 7 does not.Reproduction
npm run test:e2e.Expected: Playwright starts or reuses a server and runs the suite.
Actual:
Error: Process from config.webServer exited early.— zero tests run. A second invocation often succeeds, because by then a server is listening at the URL andreuseExistingServershort-circuits before the command is spawned. That intermittency is what makes this worth fixing rather than documenting.Environment: astro 7.1.1, @playwright/test 1.61.0, macOS (darwin 25.5.0), local only (
CIunset).Pointers
webServerblock.commandat :31,urlat :32,reuseExistingServer: !process.env.CIat :33."dev": "astro dev", the command that now early-exits."preview": "astro preview", the CI-side path that does not have this problem.npx astro dev --help— documents the Astro 7 surface added here: subcommandsstop/status/logs, and the flags--background,--port, and--ignore-lock("Start the dev server even if another one is already running, without checking or writing the lock file").Suggested directions
Pick one; they are listed roughly easiest-to-most-robust. Do not do all of them.
--ignore-lockto the dev command used by Playwright. Smallest change, but two servers then race for the same port.astro dev --port 4331) and updatewebServer.urlto match, so parallel worktrees stop colliding. Noteurland the port must stay in sync.npm run build && npm run preview). Most faithful to what CI verifies; costs a build on every local e2e run, and the comment at playwright.config.ts:27-30 explains whyastro devwas chosen locally in the first place — preserve that reasoning if you change it.Constraints
webServer.command— CI is green today andastro previewavoids the Vite dep-optimizer race described at playwright.config.ts:27-30.astro dev stopbefore testing" is a workaround, not a fix.Acceptance criteria
npm run test:e2eruns the suite instead of failing withwebServer exited early.npm run test:e2estill runs the suite.npm run test:e2einvocations both pass, with no manual cleanup between them.CIbranch ofwebServer.commandis unchanged andE2E (Playwright)still passes onmain.astro devprocess is left listening after the run that did not exist before it.Out of scope
astro.config.mjsoptimizeDepstuning.Reasoning guidance
Reproduce the failure first — start a dev server on 4321, then run the suite in a second checkout and confirm you see
webServer exited earlybefore changing anything. The failure is order-dependent and a fix that is not verified against an already-occupied port will look like it works on the second run for the wrong reason.