Wait for an element to disappear
Let a spinner, loader, or toast finish before you assert on what replaces it.
A loading spinner blocks the content you want to check. Wait for the spinner to go, not for the clock to run out.
Assert that it is hidden
toBeHidden() polls until the element is gone or invisible, then returns. It is a web-first assertion, so it waits for the page state instead of a fixed delay.
use function Playwright\Testing\expect;
$page->getByRole('button', ['name' => 'Load report'])->click();
expect($page->getByRole('status'))->toBeHidden();
expect($page->getByRole('table'))->toBeVisible();
The spinner disappears, then the table assertion runs against a settled page.
Give a slow disappearance more room
The default timeout covers most cases. When a request is genuinely slow, raise the budget on that one assertion:
use function Playwright\Testing\expect;
expect($page->locator('.spinner'))->withTimeout(15000)->toBeHidden();
withTimeout() takes milliseconds. Keep the higher value local to the slow step rather than raising it for the whole run.
When you need the wait without an assertion
If you only want to pause until the node leaves the DOM, waitForSelector() with a hidden state does that and returns nothing to assert on:
$page->waitForSelector('.spinner', ['state' => 'hidden']);
Prefer the assertion form in tests. It documents intent and fails with a clear message when the element never leaves.
The sleep trap
sleep(2); // waits for the clock, not the page
sleep(2) is wrong twice over: it is too short when the server is slow, and wasted time when the server is fast. toBeHidden() returns the moment the element is gone and no sooner.
Expected result
The blocking element is gone or hidden, and the replacement content is visible. Keep both assertions when the disappearance matters only because it reveals the next state.
Go next
- Choosing assertions: pick the assertion that matches the product signal
- Timeouts and retries: where waiting budgets come from