Skip to content
Latchkey

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.

Playwright output
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()

Locator: getByRole('button', { name: 'Submit' })
Expected: visible
Received: hidden

Diagnose 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.

Terminal
# 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-failure

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.

cart.spec.ts
await page.goto('/cart');
await page.waitForResponse('**/api/cart');
await expect(page.getByTestId('submit')).toBeVisible();

Raise the timeout for genuinely slow steps

playwright.config.ts
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 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 Playwright "expect(locator).toBeVisible() timeout" in CI?
There are 3 common causes: element renders slower than the expect timeout in ci, element exists but is hidden or covered, and selector does not match the rendered element. On a loaded runner the UI paints later than locally.
How do I fix Playwright "expect(locator).toBeVisible() timeout" in CI?
There are 2 fixes depending on which cause you have: assert on a real readiness signal and raise the timeout for genuinely slow steps. Work through them in order, since the first is the most common.
What does Playwright "expect(locator).toBeVisible() timeout" in CI actually mean?
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.
How do I stop Playwright "expect(locator).toBeVisible() timeout" in CI happening again?
Wait on responses/state, never fixed waitForTimeout sleeps. The prevention section lists 3 changes that keep it from recurring.
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.

Related guides

References

This is a timeout, not a logic error. Latchkey detects, repairs, and retries it for you. Start free → 30-day trial · No credit card