Playwright "expect(page).toHaveURL(expected)" redirect race in CI
A toHaveURL check read the address before an async redirect (login, auth callback, client-side route) completed. The web-first assertion auto-retries, so a failure means the redirect never reached the expected URL in CI, or it was slower than the expect timeout.
What this error means
The diff shows the expected URL against the current pre-redirect one, and the test flakes: it depends on how fast the navigation settles on the CI runner.
Error: expect(page).toHaveURL(expected)
Expected pattern: /\/dashboard/
Received string: "http://localhost:3000/login"
Call log:
- expect.toHaveURL with timeout 5000msCommon causes
The redirect had not completed when asserted
A client-side or auth redirect lands a moment after the action; a slow CI runner can read the URL while still on the source page.
The action that triggers navigation was not awaited
Clicking without awaiting the navigation lets the assertion run before the URL changes.
How to fix it
Wait for the navigation with the URL assertion
Use toHaveURL directly so Playwright polls until the address matches, rather than checking once.
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page).toHaveURL(/\/dashboard/);Increase the expect timeout for slow redirects
Raise the assertion timeout so a slower auth round trip in CI is not reported as a wrong URL.
await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 });How to prevent it
- Await the action that triggers navigation.
- Assert the destination URL with the retrying
toHaveURL, not a one-shot read. - Allow extra timeout for auth redirects that hit external providers.