Playwright "expect(locator).toBeVisible() timeout" in CI
Playwright auto-waited for an element to become visible and the timeout elapsed first. Usually the element renders slower than the expect timeout in CI, is covered by another element, or the selector does not match what is on the page.
What this error means
A web-first assertion fails with "Timed out 5000ms waiting for expect(locator).toBeVisible()." It often passes locally and headed but flakes on a slower CI runner where the element paints late.
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
Locator: getByRole('button', { name: 'Submit' })
Expected: visible
Received: hiddenDiagnose it: browser, server, or timing?
End-to-end failures in CI are dominated by three causes that have nothing to do with the test: the browser binary is missing, the application under test is not listening yet, or the test raced the page. Establish which before reading the assertion.
# 1. are the browsers actually installed in THIS job?
npx playwright install --with-deps chromium
npx playwright --version
# 2. is the app up before the tests start?
npx wait-on http://localhost:3000 --timeout 60000
# 3. capture evidence for the failure you cannot reproduce
npx playwright test --trace on --video retain-on-failureCommon causes
Element renders slower than the expect timeout in CI
On a loaded runner the UI paints later than locally. The default 5s expect window expires before the element is visible - a timing-only flake.
Element exists but is hidden or covered
The node is in the DOM but display:none, zero-size, or behind an overlay/cookie banner. Playwright correctly reports it as not visible.
Selector does not match the rendered element
A wrong role/name or a markup change means the locator resolves to nothing. This is deterministic, and a longer timeout will not help.
How to fix it
Assert on a real readiness signal
Wait for the network/state that gates the element, and use a resilient role/test-id locator.
await page.goto('/cart');
await page.waitForResponse('**/api/cart');
await expect(page.getByTestId('submit')).toBeVisible();Raise the timeout for genuinely slow steps
await expect(page.getByTestId('submit')).toBeVisible({ timeout: 15000 });
// or globally in playwright.config.ts: expect: { timeout: 15000 }Make the browser cache safe
- Key the browser cache to the exact test-runner version. A cache restored from a different version gives you a binary that does not match the client and fails in a way that reads like a missing install.
- Install browsers after dependencies, not before; a dependency install can replace the package that owns the browser path.
- Prefer the vendor container image when the runner allows it. It removes the whole class of missing-system-library failures.
How to prevent it
- Wait on responses/state, never fixed
waitForTimeoutsleeps. - Use role- or test-id-based locators decoupled from styling.
- Tune the global expect timeout for the slowest CI tier.