Poll until a condition is met
Let assertions retry for you, and write a bounded polling loop only for a condition no assertion covers.
Most waits are already handled for you: web-first assertions poll the page until it reaches the expected state. Write your own loop only when the condition is not something an assertion can express. When you do, bound it with a timeout and fail with a clear message. Never guess with a fixed sleep().
First, let the assertion retry
Before writing any loop, check whether an assertion already covers the case. expect() polls on its own, and you can tune both ends of that polling.
use function Playwright\Testing\expect;
expect($page->getByRole('status'))
->withTimeout(20000)
->withPollInterval(250)
->toHaveText('Ready');
withTimeout() sets the budget in milliseconds. withPollInterval() sets how often it re-checks. For text, counts, values, and visibility, this is all you need.
Poll a condition no assertion covers
When the condition is arbitrary, for example a value you compute from several reads, write a bounded loop. Track a deadline, re-check on each pass, and pause a short interval between passes.
$deadline = microtime(true) + 10.0; // 10 second budget
$pollMs = 200;
do {
$count = $page->getByRole('row')->count();
$total = (int) $page->getByTestId('total')->innerText();
if ($count === $total) {
break;
}
if (microtime(true) >= $deadline) {
throw new RuntimeException(
"Row count {$count} never matched reported total {$total}"
);
}
usleep($pollMs * 1000);
} while (true);
The loop re-reads the page each pass, so it sees fresh state. The deadline caps the wait. The exception message names what failed, so a timeout tells you why.
Why this is not a fixed sleep
usleep() here is the gap between checks, not the wait itself. The loop exits the moment the condition holds, and never runs past the deadline. A fixed sleep(10) is different in kind: it always waits the full ten seconds, and still offers no proof the condition is true when it wakes up.
sleep(10); // always slow, and never checks anything
Pitfalls
- Keep the poll interval short, but not zero. A tight busy loop hammers the transport for no gain.
- Always set a deadline. A loop with no timeout hangs the whole run when the condition never holds.
- Prefer an assertion whenever one fits. The loop is the fallback, not the default.
Expected result
The loop exits as soon as the named condition is true, or fails with a message that includes the last observed values. It should never hang indefinitely.
Go next
- Wait for text to appear: the assertion path for text
- Assert a list count: the assertion path for counts
- Timeouts and retries: how waiting budgets work