Give the poll loop room to breathe on CI, or find out the condition never becomes true there. expect.poll retries its assertion until it passes or the test timeout hits - on a slow CI runner each poll cycle costs more, so a poll that converges in 5 seconds locally can eat the full 30 second test timeout in CI. First lengthen the test timeout for that spec (or mark it slow on CI); then look at the poll intervals so you are not hammering a slow endpoint hundreds of times. If it still times out, stop tuning and check the condition itself: the state the poll waits for may never happen in CI because of missing seed data or a disabled feature. A poll that can never pass will burn any timeout you give it.

```text
playwright "Test timeout of 30000ms exceeded" on expect.poll in CI but not locally
```

## Steps
1. Mark the test as slow for CI or raise its individual timeout. This is a diagnostic step: if the test goes green with more time, the poll logic is fine and CI is just slow.
   Expected: the test passes with a longer timeout, proving the poll converges eventually.

2. Tune the poll itself: set explicit polling intervals instead of the default rapid retry, so each attempt has time to see a settled state and you are not spamming a slow CI backend.
   Expected: fewer, better-spaced poll attempts and the same eventual pass.

3. Check what the poll waits on in the CI environment: seed data, feature flags, background jobs. Run the poll condition manually against the CI-like environment and see if it ever becomes true.
   Expected: you have confirmed the polled state is reachable in CI, not just locally.

4. If the condition depends on a background job or async pipeline, make the test trigger or await that work explicitly instead of polling blindly for the full timeout.
   Expected: the test waits on the actual cause rather than polling a side effect.

5. Keep the timeout change scoped to the slow spec. A global timeout bump hides the next real slowdown across the whole suite.
   Expected: only the polling spec carries the higher timeout; the suite default is unchanged.

## Use this when

- "Test timeout exceeded" specifically on expect.poll, CI-only
- a poll that converges locally but not within the timeout on CI
- you need to separate "slow CI" from "condition never true in CI"

## Not for this skill when

- expect.poll failing fast with an assertion error rather than a timeout (that is a wrong expectation, fix the assertion)
- the polled condition never becoming true locally either (that is an app bug - file it against the app)

## Variant phrasings

### playwright expect.poll timeout

The general form. CI-only adds the slow-runner versus unreachable-condition fork.

### test timeout exceeded playwright CI

Broader phrasing. When poll is in the stack, treat it as a polling problem first.

## Why it happens

expect.poll wraps an assertion in a retry loop bounded by the test timeout, not by its own limit. Locally the loop converges quickly because every poll cycle is fast; in CI each cycle is slower (loaded CPU, slower network to the app under test), so the same number of cycles consumes far more wall-clock time and the test timeout fires first. Separately, if the state being polled depends on CI-specific setup that is missing, the loop retries until the timeout no matter how long you make it - which is why the reachability check matters more than the timeout value.

## Edge cases

- polling an external service with rate limits: rapid default retries can get your CI IP throttled, making the poll slower the longer it runs
- expect.poll inside a test that also has a tight expect timeout: the two timeouts interact - the poll inherits the test timeout unless told otherwise
- flaky pass after lengthening the timeout: if it passes sometimes at the longer timeout, the condition is racy in CI - fix the underlying timing instead of the timeout

## Provenance

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