diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 1acaa8cbdbe..637951887f1 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -76,11 +76,19 @@ export async function login(page: Page, options: LoginOptions = users.owner) { // biome-ignore lint/suspicious/noExplicitAny: reset the current user (ctx as any)[Symbol.for("currentUser")] = undefined; await ctx.clearCookies(); - await page.goto("/login"); + await page.goto("/login", { waitUntil: "domcontentloaded" }); await page.getByLabel("Email").fill(options.email); await page.getByLabel("Password").fill(options.password); await page.getByRole("button", { name: "Sign In" }).click(); - await expectUrl(page).toHavePathName("/workspaces"); + // Sign-in triggers a hard navigation to "/", then React Router + // client-side redirects to "/workspaces" without firing a load event. + // waitForURL alone resolves on the URL change, before WorkspacesPage + // has mounted. The title check is the actual synchronization point: + // it retries until the page component renders. Removing either wait + // reintroduces a navigation race in tests that goto() right after + // login. See https://github.com/coder/coder/pull/27107. + await page.waitForURL((url) => url.pathname === "/workspaces"); + await expect(page).toHaveTitle("Workspaces - Coder"); // biome-ignore lint/suspicious/noExplicitAny: update once logged in (ctx as any)[Symbol.for("currentUser")] = options; }