## TL;DR

Playwright auto-waits, so a selector timeout means the element never reached an actionable state. Use the inspector to test the locator live, then fix the locator, the visibility precondition, or the app state.

## Error

```text
Error: locator.waitFor: Timeout 5000ms exceeded.
waiting for locator('[data-testid="checkout"]') to be visible
```

## Steps

1. Run with `--debug` and use the Playwright Inspector to evaluate the locator against the live page. Expected: you see whether it matches zero, one, or many elements.
2. If it matches zero, fix the locator: prefer `getByRole`, `getByTestId`, or `getByText` over CSS. Expected: exactly one match.
3. If it matches but is hidden, reproduce the user steps that reveal it (open the menu, complete the form). Expected: the locator becomes visible through the real flow.
4. If the element appears after data loads, wait on the data URIs `await page.waitForResponse('**/api/cart')` before the locator. Expected: deterministic instead of racy.
5. Only then consider raising the timeout: `locator.waitFor({ state: 'visible', timeout: 15000 })`. Expected: used sparingly, on the slow locator.

## When to use

- `locator.waitFor` or auto-wait timeouts at the default 5s.
- The inspector shows the locator state clearly.

## When not to use

- Test timeout of 30000ms (whole-test slowness).
- `strict mode violation` (too many matches, not zero).

## Tool compatibility

- Playwright 1.30 through latest; `--debug` inspector.

## Variant phrasings

### waiting for getByRole to be visible timed out

Same diagnosis; role locators are preferred but still need the element to exist.

### locator never became stable

The element matched but kept moving (animation); wait for the animation to end.

## Why it happens

Auto-waiting checks actionability: attached, visible, stable, enabled. A timeout names which check never passed, which points at the cause.

## Edge cases

- `state: 'attached'` passes for hidden elements; use it only when visibility is genuinely not required.
- Elements in closed shadow roots are invisible to all locators; that is a testability bug to fix in the app.
- Animations that never settle (spinners) make `stable` never pass; assert on the loaded state instead.

## Provenance

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