## TL;DR
toBeVisible auto-retries for the assertion timeout (default 5s), so a failure means the element never became visible in that window. Confirm the locator matches something attached to the DOM first, then fix the app state or timing that keeps it hidden. Raising the timeout is the last resort, not the first.

## Error
```text
Error: expect(locator).toBeVisible() failed
Locator: getByRole('dialog', { name: 'Confirm delete' })
Expected: visible
Received: hidden
```

## Steps
1. Temporarily change the assertion to `toBeAttached()` and re-run. Expected: this tells you whether the locator is wrong (still fails) or the element exists but stays hidden.
2. If not attached: fix the locator using the trace viewer's DOM snapshot. Expected: `toBeAttached()` passes.
3. If attached but hidden: drive the UI state that reveals it (click the opener, wait for the API response with `waitForResponse`), or check for a CSS or overlay issue in manual reproduction. Expected: the element becomes visible when you reproduce by hand.
4. If the UI is just slow, extend the assertion: `await expect(dialog).toBeVisible({ timeout: 10000 })`. Expected: the assertion passes on slow runs.

## When to use
- `expect(locator).toBeVisible()` fails after the full assertion timeout.
- A dialog, toast, or dropdown never shows up in the test.
- You need to separate "wrong locator" from "right locator, hidden element".

## When not to use
- The element is visible but a click is intercepted (that is a pointer-events problem, not visibility).
- An action like `click()` times out rather than an assertion.
- The element is inside an iframe (scope with `frameLocator` first).

## Tool compatibility
- Playwright 1.40 through 1.5x, `@playwright/test` runner.
- Web-first assertions (`toBeVisible`, `toBeAttached`, `toBeHidden`) all share the same retry behavior.

## Variant phrasings
### expect locator toBeVisible timed out after 5000ms
Same failure with timeout wording. Same steps: check attached first.
### element is not visible in playwright assertion
Usually the same toBeVisible failure. Confirm the locator matches a node before debugging visibility.

## Why it happens
toBeVisible requires a non-empty bounding box and no `visibility: hidden`. Common causes are unmet app state (the dialog never opened, the data never loaded), the element rendering at zero size or off-screen, or a wrong locator matching a hidden duplicate node.

## Edge cases
- The locator matches a hidden duplicate while the visible node has a different name: tighten the name or scope the locator.
- Mid-animation: the element is transitioning, assert on a stable parent or wait for the animation to settle.
- toBeVisible passes but the click misses: an overlay is intercepting pointer events, a different failure mode.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_edoH9Hs89peHLsx2CKL3DQ
