fix(site/e2e): fix login helper race condition causing navigation flake - #27107
Conversation
The login() helper waited for the post-login URL to match /workspaces
via polling, but did not wait for the page to finish loading. When a
test immediately navigated elsewhere after login(), the still-loading
page could trigger a competing navigation, causing:
page.goto: Navigation to "/deployment/users" is interrupted by
another navigation to "/"
Replace the URL polling with waitForLoadState("domcontentloaded"),
which waits for the hard navigation (location.href assignment in
LoginPage.tsx) to complete. This is the correct completion signal
since login uses a full page reload, not SPA routing.
This also removes the coupling between login() and the /workspaces
landing page. A dedicated test in login.spec.ts now covers the
post-login redirect separately.
waitForLoadState("domcontentloaded") resolves immediately on the
current page's DOM (the login page) before the hard navigation
triggered by location.href even starts. This caused login() to return
before the session cookie was set, breaking all downstream tests with
"session token not found".
Use page.waitForURL(/\/workspaces/) instead. This waits for the
actual hard navigation to complete and the new page to fully load
(default waitUntil: "load"), which guarantees the session cookie
is present.
The login() helper already waits for /workspaces via waitForURL, so a separate test asserting the same thing adds no coverage.
…race waitForURL resolves once the URL matches and the document's load event fires. But login triggers a hard nav to "/" (the default redirect), and React Router then does a client-side redirect to "/workspaces". The load event fires for the "/" page load, not the client-side redirect, so waitForURL resolves before the /workspaces page components have mounted. Wait for the page title to match /Workspaces/ after waitForURL. The title is set by the WorkspacesPage component, so it proves React booted, auth resolved, and the page rendered. This closes the window where a subsequent page.goto could be interrupted by pending React rendering.
|
/coder-agents-review |
|
Chat: Review posted | View chat Review history
deep-review v0.9.0 | Round 1 | Last posted: Round 1, 3 findings (1 P3, 2 Nit), COMMENT. Review Finding inventoryFinding inventory - PR #27107Findings
Round logRound 1Netero clean (no findings). Panel: bisky, hisoka, mafu-san, mafuuu, pariston, komugi, ging-ts, gon, leorio, chopper, kite, meruem (+meruem wildcard). Reviewed against 990f0a5..dde31a8. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
Clean, tightly scoped flake fix: one helper, three lines, one concern. The panel tried hard to break it and could not. The diagnosis in the PR body is accurate at every link the reviewers traced (LoginPage's hard location.href nav to /, the router's client-side <Navigate to="/workspaces" replace />, and WorkspacesPage setting the Workspaces - Coder title), and the switch from a bare page.url() poll to a rendered-signal wait is the durable fix, not a widened timeout. Pariston: "I tried to build a case against this and could not."
Severity count: 0 P0-P2, 1 P3, 2 Nit.
One substantive point worth acting on before merge (P3): the toHaveTitle line is load-bearing for the fix but looks like a duplicate of the waitForURL above it, so a future reader is likely to delete it and reinstate the flake. Three reviewers independently flagged this deletion risk. A one-line comment fixes it.
Two process observations, no action required but worth knowing:
- The description credits
waitForURL(/\/workspaces/)with waiting for a load state. The/to/workspacestransition is a same-document History navigation that fires noloadevent, sowaitForURLresolves on the URL change alone; the actual synchronization istoHaveTitle, which retries untilWorkspacesPagemounts. The fix is correct; the stated mechanism is imprecise, and this reinforces the P3 (do not trimwaitForURLor the title check believing the other carries the guarantee). "Fully rendered" also slightly overstates what the title wait proves: it proves mount, not that the workspaces query settled. - Commit subject is 73 chars (one over the 72 ceiling), and the PR title stutters
fix(site/e2e): fix .... Minor; the commit body itself is exemplary.
🤖 This review was automatically generated with Coder Agents.
The toHaveTitle check looks redundant next to waitForURL but is the actual synchronization point, since the client-side redirect from / to /workspaces fires no load event. Document this so the check is not removed as an apparent duplicate.
The regex /\/workspaces/ is an unanchored partial match, so any URL containing /workspaces anywhere in it would satisfy the wait. A plain string without wildcards resolves against baseURL and requires exact equality, which matches the post-login redirect precisely.
The exact-string form of waitForURL compares the full URL including any query string, which is stricter than intended. Use the predicate form to compare only the pathname, which precisely matches the post-login redirect regardless of query parameters.
Matches the file's exact-string toHaveTitle convention and catches title drift that a substring regex would miss.
Fixes flake reported in DEVEX-538.
Problem
The
login()e2e helper had a race condition causing intermittent navigation failures:After clicking Sign In,
LoginPage.tsxdoes a hard navigation vialocation.href = sanitizeRedirect(redirectTo). With no?redirect=param,retrieveRedirectdefaults to"/", so login navigates to/. The browser loads/, fires theloadevent, then React boots and the router does a client-side redirect from/to/workspaces(via<Navigate to="/workspaces" replace />).The old helper waited with
expectUrl(page).toHavePathName("/workspaces"), which pollspage.url()and resolves the moment the pathname matches. It has no awareness of page load state. So it resolved after the client-side redirect changed the URL, but before the/workspacespage components had mounted. When a test immediately calledpage.goto()afterward, pending React rendering could trigger a competing navigation.Fix
Replace the URL polling with two Playwright-idiomatic waits:
page.waitForURL(/\/workspaces/)hooks into the browser's navigation lifecycle: it waits for the URL to match AND for the page to reach a load state ("load"by default), unlikeexpectUrlwhich is purely a string poll.await expect(page).toHaveTitle(/Workspaces/)waits for the page title, which is set by theWorkspacesPagecomponent. This proves React booted, auth resolved, and the page fully rendered, closing the window where pending React work could interfere with the next navigation.Also adds
{ waitUntil: "domcontentloaded" }topage.goto("/login")for consistency with every other navigation helper in the file.