Playwright "expect(locator).toBeVisible() timeout" in CI
By Kaveh Alemi·Latchkey
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.
Playwright output
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
Locator: getByRole('button', { name: 'Submit' })
Expected: visible
Received: hidden
Common 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 expect(page.getByTestId('submit')).toBeVisible({ timeout: 15000 });
// or globally in playwright.config.ts: expect: { timeout: 15000 }
How to prevent it
Wait on responses/state, never fixed waitForTimeout sleeps.
Use role- or test-id-based locators decoupled from styling.
Tune the global expect timeout for the slowest CI tier.
Frequently asked questions
What causes ""expect(locator).toBeVisible() ... timeout""?
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.
How do I fix "expect(locator).toBeVisible() ... timeout"?
Wait for the network/state that gates the element, and use a resilient role/test-id locator.
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.