In Playwright, wait for the condition that proves the page is ready—not an arbitrary number of milliseconds. Locator actions such as click(), fill(), and check() automatically wait for the target to become actionable. Web-first assertions such as toBeVisible() and toHaveText() retry until the expected state is true. Use an explicit wait only when it expresses a real requirement, such as a locator becoming visible, a navigation reaching a specific lifecycle state, or a popup being created.
The waiting model Playwright expects
Playwright synchronizes tests around observable conditions. Before an action runs, it resolves the locator and performs the relevant actionability checks. The official auto-waiting documentation says: “It auto-waits for all the relevant checks to pass and only then performs the requested action.” This removes most manual sleeps from a test.
After an action, assert the state your user or test actually needs. Assertions re-fetch and re-test the target until the condition passes or the assertion timeout is reached. The documented default timeout for web assertions is 5 seconds (Microsoft Playwright documentation accessed September 2026; defaults can change between releases).
What an action waits for
For a normal locator action, Playwright checks conditions such as whether the element resolves, is visible, is enabled, is stable enough to interact with, and is not covered by another element. The exact checks depend on the action. A successful click therefore means more than “the selector exists”; it means Playwright could perform the interaction.
#1 Best Overall
const save = page.getByRole('button', { name: 'Save' });
await save.click();
Do not add a delay before this click merely because the button appears after an animation. Use a locator that identifies the intended control and let the action wait for actionability.
What assertions wait for
Assertions are the right tool for a resulting UI state. They retry the locator and condition until success or timeout.
await save.click();
await expect(page.getByRole('status')).toHaveText('Saved');
This proves that the save operation produced the expected user-visible result. A fixed delay would prove only that a certain amount of time elapsed.
Choose the wait that matches the condition
| Need | Preferred API | What it proves | Retry behavior |
|---|---|---|---|
| Interact with a control | locator.click(), fill(), check() |
The locator became actionable for that action | Built in, until the action timeout |
| Verify visible text or state | expect(locator).toBeVisible(), toHaveText(), toHaveCount() |
The expected UI condition is true | Web-first retry until assertion timeout |
| Wait for a specific DOM state | locator.waitFor({ state }) |
The node is attached, visible, hidden, or detached | Waits until the requested state or timeout |
| Wait for navigation lifecycle | page.waitForLoadState() |
A documented load state was reached | Waits for that lifecycle event |
| Coordinate a browser event | page.waitForEvent() |
The event triggered by an action occurred | Promise resolves when the event fires |
Wait for an element with a locator
locator.waitFor() supports four states: attached, detached, visible, and hidden. Its default state is visible.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →const orderSent = page.locator('#order-sent');
await orderSent.waitFor({ state: 'visible' });
Use the state that matches your requirement
attached: the node is present in the DOM. It may still be hidden or covered.visible: the node is present and visible to the user. Use this when the user must see it.hidden: the node is hidden or no longer present. This is useful for waiting out a spinner or blocking overlay.detached: the node has been removed from the DOM.
For most test intent, an assertion communicates more clearly than a bare wait. For example, use await expect(orderSent).toBeVisible() when visibility is a behavior you want the test to verify. Use waitFor() when you need synchronization without making an assertion the test’s subject.
Rank #2
Wait after clicking without guessing a delay
First click, then wait for evidence of the result. The evidence might be a status message, a URL, a newly rendered panel, or a row count.
await page.getByRole('button', { name: 'Submit order' }).click();
await expect(page.getByRole('status')).toHaveText('Order submitted');
If the click causes navigation, assert the destination as well as (or instead of) a generic load event:
await page.getByRole('link', { name: 'Account' }).click();
await page.waitForLoadState('domcontentloaded');
await expect(page).toHaveURL(/account/);
Most actions already wait for relevant readiness. A load event alone does not prove that application data, a client-side route, or a particular control is ready. The URL or page content is usually stronger evidence.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsCoordinate popups and other events
Create the event promise before the action that triggers it. Otherwise, a fast popup can be missed.
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open report' }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
The same pattern applies to other page events: establish the listener, perform the triggering action, then await the event result.
Why waitForTimeout() causes flaky tests
await page.waitForTimeout(1000) pauses for exactly one second regardless of whether the page is ready. If the operation finishes in 100 ms, the test is unnecessarily slow; if it needs 1.5 seconds, the test fails intermittently. The Page API gives the production guidance: “Never wait for timeout in production.” Treat this method as a debugging aid only.
Replace a sleep with the condition that explains why the delay was added:
Recommended Free Tools
- Waiting for a toast:
await expect(page.getByRole('status')).toBeVisible(). - Waiting for a loading mask to finish:
await expect(page.locator('.loading-mask')).toBeHidden(). - Waiting for a list:
await expect(page.getByRole('row')).toHaveCount(10), when ten rows is the contract. - Waiting for a route:
await expect(page).toHaveURL(/orders/).
Why networkidle is not a general readiness signal
The networkidle load state represents at least 500 ms with no network connections. Playwright labels it discouraged for testing because an application can continue rendering after that quiet period, and analytics, polling, WebSockets, or lazy requests can prevent a stable “idle” moment. Use it only when network quiescence is the actual condition you need. For ordinary tests, assert the page state that matters: a heading, table, button, URL, or completion message.
Dynamic lists and locator.all()
For a list that is still rendering, wait for a stable condition before collecting items. locator.all() returns immediately and does not wait for matches.
const rows = page.getByRole('row');
await expect(rows).toHaveCount(10);
const renderedRows = await rows.all();
If the count is variable, wait for a more meaningful completion marker or assert a minimum/known state before calling all(). Otherwise, the test can observe a partially rendered list.
Rank #4
Timeouts: scope, configuration, and diagnosis
Every wait has a timeout scope. Action timeouts govern operations such as clicks; assertion timeouts govern web-first assertions; explicit waits use their configured timeout. The documented default assertion timeout is 5 seconds. Set a longer timeout only when the product’s real behavior requires it, not to conceal a weak locator or a race.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
await expect(page.getByRole('status')).toHaveText('Saved', { timeout: 10000 });
Keep a targeted override close to the slow assertion. A globally huge timeout makes failures take longer and can hide regressions.
Troubleshooting common wait failures
“Locator resolved to hidden element”
The selector may match a template node, a collapsed menu item, or an element behind a responsive layout. Prefer a role, label, or test identifier that identifies the visible control. If visibility itself is the requirement, wait for or assert visible.
“Element is not receiving pointer events”
An overlay, cookie dialog, animation, or sticky header may cover the target. Wait for the overlay to be hidden, dismiss it through the UI, or choose the correct visible locator. Avoid forcing the click unless bypassing actionability is explicitly what the test is intended to check.
“Strict mode violation” or multiple matches
Your locator is ambiguous. Narrow it with an accessible name, a container, or a more specific role. A wait cannot decide which of several matching controls is correct.
Assertion times out
Check the expected text, state, and timing. Capture a trace or inspect the page at failure. The application may show a different message, fail an API request, or require an additional user action. Increasing the timeout is appropriate only after confirming that the condition is correct but legitimately slow.
Navigation wait never resolves
Not every click navigates. Single-page applications may update content without a traditional navigation, and a link may open a popup. Wait for the URL, content, or event that actually represents the behavior instead of assuming a navigation.
Spinner wait is unreliable
A spinner can be recreated between renders or removed before the test starts waiting. Prefer a completion assertion (such as a result heading or row count). If the disappearance itself is the contract, use a stable locator and toBeHidden().
A practical decision procedure
- Identify the outcome the test needs: an actionable control, visible content, a DOM transition, navigation, or an event.
- Use the corresponding locator action, assertion,
waitFor(), load-state wait, or event promise. - Make the locator specific and user-oriented where possible: roles, labels, and accessible names are usually more robust than styling classes.
- Set a timeout that reflects the operation’s real upper bound.
- When a wait fails, inspect the locator and the actionability or assertion error before adding any delay.
Or skip the browser setup
If your goal is a rendered screenshot rather than an end-to-end interaction, ScreenshotNeo makes one GET request and returns a PNG, JPEG, WebP, or PDF. Its capture options include waiting for a selector, a delay, or network idle, plus full-page loading, custom CSS and JavaScript, device presets, cookies, headers, geolocation, and more. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Use the API details in the ScreenshotNeo documentation. cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Should I use waitForSelector()?
Prefer a locator plus a web-first assertion when you are verifying UI behavior. An explicit locator wait is appropriate when you need synchronization with a specific DOM state without making that state the assertion under test.
What is Playwright’s default assertion timeout?
The documented default is 5 seconds. Playwright defaults can change, so verify the documentation version used by your project.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Can I wait for a fixed delay while debugging?
Yes, a temporary page.waitForTimeout() can help inspect a race locally. Remove it from production tests and replace it with a condition.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

