playwright "Test timeout of 30000ms exceeded" on expect.poll in CI but not locally
Fixes Playwright "Test timeout of 30000ms exceeded" on expect.poll that only reproduces in CI. Explains why poll loops burn the whole test timeout on slow runners, and shows how to scope timeouts, lengthen poll intervals, and verify the polled condition is reachable in CI. Use when expect.poll passes locally but exhausts the timeout in CI. Not for conditions that never become true anywhere (an app bug), and not for timeouts unrelated to polling.
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.
playwright "Test timeout of 30000ms exceeded" on expect.poll in CI but not locallySteps
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.
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.
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.
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.
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