Skip to content

fix(testing): reserve dev-server ports in the react-router e2e suite - #36585

Merged
FrozenPandaz merged 2 commits into
masterfrom
fix/e2e-react-router-reserve-port
Aug 5, 2026
Merged

fix(testing): reserve dev-server ports in the react-router e2e suite#36585
FrozenPandaz merged 2 commits into
masterfrom
fix/e2e-react-router-reserve-port

Conversation

@FrozenPandaz

Copy link
Copy Markdown
Contributor

Current Behavior

Both e2e cases in e2e/react/src/react-router-ts-paths.test.ts generate their app without --port, so both take the 4200 default: the should execute e2e tests using playwright case, and the should execute e2e tests using cypress case that runs right after it.

When the first case's dev server has not released 4200 by the time the second case's server probes it, the second server relocates itself — but nothing tells Playwright, whose baseURL is still 4200:

Port 4200 is in use, trying another one...
  ➜  Local:   http://localhost:4201/
...
Error: page.goto: Could not connect to localhost: Connection refused
  - navigating to "http://localhost:4200/", waiting until "load"

A detail worth noting, because it makes the failure look stranger than it is: two of the three browsers pass. Chromium and Firefox connect to 4200 fine and only webkit fails. The leftover server from the previous case is still serving 4200, and since it is the same generated react-router app the h1 says "Welcome" and the assertions hold — until that server exits mid-run and the last browser hits a dead port.

Observed on 2026-08-05, and this suite also failed in master run 30947753956 (2026-08-04), though I did not keep that log so I cannot confirm the same cause.

Expected Behavior

Each app gets its own reserved dev-server port, so the two cases cannot contend for 4200 at all.

reservePort() (e2e/utils/port-utils.ts) claims a port via an atomic lock file and verifies the OS port is actually free, scanning from 6100 — deliberately outside the framework-default zone of 3000/4200/5173/8080. So the reserved ports collide neither with each other nor with a parallel suite that generated an app on a framework default.

The port is passed as --port, which @nx/react:app already threads into both ends of the problem — the vite dev server (createOrEditViteConfig's port/previewPort) and the Playwright config's baseURL and webServer.url (via getReactRouterE2EWebServerInfo). No config file surgery needed; this is the same approach react-vite.test.ts already uses for its custom-port case.

killPorts() is called after each e2e run. That part is hygiene rather than correctness — the distinct reserved ports are what make the collision structurally impossible — it just stops dev servers accumulating on the agent.

Related Issue(s)

N/A


Polygraph View session ↗

Both e2e cases in this suite generated their app without --port, so both took
the 4200 default. When the playwright case's dev server had not released 4200
by the time the cypress case's server probed it, the second server relocated to
4201 while Playwright kept navigating to 4200 — "Could not connect to
localhost: Connection refused". Two of the three browsers still passed, because
the leftover server was serving an identical app until it exited mid-run.

Reserve a port per app via reservePort() and pass it as --port, which the react
generator threads into both the vite dev server and the playwright config's
baseURL/webServer url. reservePort() scans from 6100, outside the
framework-default zone, so the reserved ports cannot collide with each other or
with a parallel suite on a generator default.
@netlify

netlify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 4cf2083
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a73a8f6684706000882878a
😎 Deploy Preview https://deploy-preview-36585--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 4cf2083

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 29m 6s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 7s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 59s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 18s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-05 21:53:23 UTC

@FrozenPandaz
FrozenPandaz marked this pull request as ready for review August 5, 2026 21:15
@FrozenPandaz
FrozenPandaz requested a review from a team as a code owner August 5, 2026 21:15
@netlify

netlify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 4cf2083
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a73a8f6684706000882878c
😎 Deploy Preview https://deploy-preview-36585--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

A later failure showed the collision is not confined to one file. The
ts-solution suite (pid 32156) failed the same way while the ts-paths suite (pid
29595) was running alongside it on the same agent — its own log records
"Cypress installed by process 29595". Both suites put every dev server on the
4200 default, so the loser relocates to 4201 while Playwright keeps navigating
to 4200.

reservePort()'s lock files are shared across processes, so reserving in both
suites is what actually separates them. Corrects the comment in ts-paths, which
blamed the two cases within that one file.
@FrozenPandaz
FrozenPandaz enabled auto-merge (squash) August 5, 2026 22:01
@FrozenPandaz
FrozenPandaz merged commit f977b00 into master Aug 5, 2026
25 checks passed
@FrozenPandaz
FrozenPandaz deleted the fix/e2e-react-router-reserve-port branch August 5, 2026 22:13
polygraph-snapshot-app Bot pushed a commit that referenced this pull request Aug 11, 2026
…36585)

## Current Behavior

Both e2e cases in `e2e/react/src/react-router-ts-paths.test.ts` generate
their app without `--port`, so both take the `4200` default: the `should
execute e2e tests using playwright` case, and the `should execute e2e
tests using cypress` case that runs right after it.

When the first case's dev server has not released `4200` by the time the
second case's server probes it, the second server relocates itself — but
nothing tells Playwright, whose `baseURL` is still `4200`:

```
Port 4200 is in use, trying another one...
  ➜  Local:   http://localhost:4201/
...
Error: page.goto: Could not connect to localhost: Connection refused
  - navigating to "http://localhost:4200/", waiting until "load"
```

A detail worth noting, because it makes the failure look stranger than
it is: **two of the three browsers pass.** Chromium and Firefox connect
to `4200` fine and only webkit fails. The leftover server from the
previous case is still serving `4200`, and since it is the same
generated react-router app the `h1` says "Welcome" and the assertions
hold — until that server exits mid-run and the last browser hits a dead
port.

Observed on 2026-08-05, and this suite also failed in master run
[30947753956](https://github.com/nrwl/nx/actions/runs/30947753956)
(2026-08-04), though I did not keep that log so I cannot confirm the
same cause.

## Expected Behavior

Each app gets its own reserved dev-server port, so the two cases cannot
contend for `4200` at all.

`reservePort()` (`e2e/utils/port-utils.ts`) claims a port via an atomic
lock file and verifies the OS port is actually free, scanning from
`6100` — deliberately outside the framework-default zone of
`3000`/`4200`/`5173`/`8080`. So the reserved ports collide neither with
each other nor with a parallel suite that generated an app on a
framework default.

The port is passed as `--port`, which `@nx/react:app` already threads
into both ends of the problem — the vite dev server
(`createOrEditViteConfig`'s `port`/`previewPort`) and the Playwright
config's `baseURL` and `webServer.url` (via
`getReactRouterE2EWebServerInfo`). No config file surgery needed; this
is the same approach `react-vite.test.ts` already uses for its
custom-port case.

`killPorts()` is called after each e2e run. That part is hygiene rather
than correctness — the distinct reserved ports are what make the
collision structurally impossible — it just stops dev servers
accumulating on the agent.

## Related Issue(s)

N/A

<!-- polygraph-session-start -->
---
<p><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-dark.svg"><img
src="https://static.ops.cloud.nx.app/polygraph/session-logo-v4-light.svg"
width="16" height="22" align="middle" alt="Polygraph"></picture> <a
href="https://app.trypolygraph.com/orgs/6a061dcb561c062131116eca/sessions/Reserve-dev-server-ports-in-the-react-router-e2e-suite-be8f4ac5">View
session ↗</a></p>
<!-- polygraph-session-end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants