Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
In Playwright, wait for a condition—not an arbitrary number of milliseconds. Most locator actions already wait for the element to become actionable, and web-first assertions retry until the expected UI state appears. Use an explicit locator, navigation, or event wait only when that condition is what your test needs to prove.
Which Playwright wait should you use?
| What the test needs | Preferred approach | What it establishes |
|---|---|---|
| Click, fill, or check a control | Call the locator action directly | Playwright waits for the relevant actionability checks before acting. |
| Confirm a result in the UI | Use a web-first assertion such as toHaveText() or toBeVisible() |
The assertion retries until the condition is true or its timeout expires. |
| Wait for a specific element state | Use locator.waitFor({ state }) |
The locator reaches attached, detached, visible, or hidden state. |
| Wait for a navigation condition | Assert the destination URL or content; use a load state only if needed | The page has reached the condition relevant to the test. |
| Capture a popup or other event caused by an action | Start a waitForEvent() promise before the action |
The test does not miss the event while waiting for it. |
Playwright’s auto-waiting documentation says: “It auto-waits for all the relevant checks to pass and only then performs the requested action.” Playwright: Auto-waiting.
Let locator actions wait for actionability
For a normal interaction, use a locator and perform the action. Do not add a fixed sleep just to give the page time to settle.
Recommended Free Tools
import { test, expect } from '@playwright/test';
test('saves a profile', async ({ page }) => {
await page.goto('https://example.com/profile');
const save = page.getByRole('button', { name: 'Save' });
await save.click();
});
Actions such as click(), fill(), and check() wait for the locator to resolve and pass the checks relevant to that action. A click, for example, needs a target that can receive the interaction; a hidden or disabled control is not made usable merely by sleeping first.
#1 Best Overall
Prefer locators that identify the intended control clearly, such as getByRole() with an accessible name. If an action times out, verify the locator matches the right element and investigate whether the element is hidden, disabled, animating, covered by an overlay, or duplicated.
Assert the state produced by an action
A successful click proves the action could be performed; it does not by itself prove the application completed the work. Follow the action with an assertion on the resulting state.
import { test, expect } from '@playwright/test';
test('shows a save confirmation', async ({ page }) => {
await page.goto('https://example.com/profile');
const save = page.getByRole('button', { name: 'Save' });
await save.click();
await expect(page.getByRole('status')).toHaveText('Saved');
});
Web-first assertions such as toBeVisible(), toHaveText(), and toHaveCount() re-check the locator until the expected result is true or the assertion timeout is reached. This retry behavior is useful when a UI update takes a variable amount of time.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →The documented default timeout for web assertions is 5 seconds in Microsoft Playwright documentation accessed on September 29, 2026; the default can be changed in project configuration or for an individual assertion. Check the documentation for the Playwright version your project uses before depending on a default.
Rank #2
Set a timeout when the condition genuinely needs longer
If a particular result is expected to take longer than the default, scope a larger timeout to that assertion rather than inserting a sleep:
await expect(page.getByRole('status'))
.toHaveText('Report ready', { timeout: 15_000 });
A larger timeout gives a slow operation more time; it does not make an incorrect locator or broken application succeed. Keep timeouts as narrow as possible so failures identify the slow or missing condition.
Wait for a locator state explicitly
Use locator.waitFor() when the state itself is the condition you need. The supported states are attached, detached, visible, and hidden. The default is visible.
const orderSent = page.locator('#order-sent');
await orderSent.waitFor({ state: 'visible' });
attached: the element exists in the DOM. It need not be visible.visible: the element is visible to the user.hidden: the element is hidden or absent.detached: the element is no longer attached to the DOM.
For example, if a loading indicator should disappear before a result can be checked:
Rank #3
await page.locator('[role="progressbar"]').waitFor({ state: 'hidden' });
await expect(page.getByRole('heading', { name: 'Results' })).toBeVisible();
When the purpose is to verify meaningful page behavior, an assertion often communicates intent better than a generic wait. Use waitFor() for synchronization and an assertion for the outcome the test must validate.
Wait for navigation only when navigation is the condition
After clicking a link, assert the destination the user should reach. A load event can be useful when the test specifically depends on that lifecycle milestone, but it is not a substitute for checking that the application is ready.
await page.getByRole('link', { name: 'Account' }).click();
await page.waitForLoadState('domcontentloaded');
await expect(page).toHaveURL(/account/);
await expect(page.getByRole('heading', { name: 'Account' })).toBeVisible();
Most locator actions already wait for the readiness relevant to the action. A page can finish a load event while client-side data is still rendering, so a URL, heading, status, or other user-visible assertion is usually stronger evidence that the destination works.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesDo not use network idle as a generic readiness signal
networkidle represents at least 500 ms with no network connections. Playwright labels it discouraged for testing as a general readiness check. Pages that poll, stream, or load analytics may not become idle, while a quiet network does not prove the interface is usable. Wait for the specific UI state the test depends on instead.
Coordinate popups and other events
When an action triggers an event, create the event promise before performing the action. Otherwise, a fast event could occur before the test starts waiting.
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open report' }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
await expect(popup).toHaveURL(/report/);
This ordering applies to events that must be correlated with a particular action: register the wait, trigger the action, then await the event and assert the resulting state.
Why fixed sleeps and broad waits cause flaky tests
await page.waitForTimeout(1000) waits exactly one second whether the page is ready after 50 ms or still incomplete after the full second. The first case wastes time; the second can still fail. Playwright’s Page API says, “Never wait for timeout in production.” It describes timeout waits as a debugging aid: Playwright: Page API.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Similarly, page.waitForSelector('.toast') is discouraged when a locator and assertion express the intended condition more clearly:
// Avoid treating a fixed pause as readiness:
await page.waitForTimeout(1000);
// Prefer waiting for the condition the test needs:
await expect(page.locator('.toast')).toBeVisible();
Do not use networkidle as a universal replacement for a sleep. It is a network lifecycle condition, not proof that a particular UI update completed.
Handle dynamic lists without racing the page
locator.all() returns immediately; it does not wait for matching elements to appear or for a changing list to finish rendering. Establish a stable condition first, then read the elements.
const rows = page.getByRole('row');
await expect(rows).toHaveCount(4);
const allRows = await rows.all();
Choose a count or completion assertion that fits the application. If the list can legitimately change, assert a specific row, label, or completion state rather than a count that is not stable by design.
Troubleshoot wait timeouts
- The locator never matches: Check the role, accessible name, selector, and page state. Prefer a user-facing locator when available.
- The target is present but not visible: Confirm the right element was selected and that a modal, responsive layout, or conditional render is not keeping it hidden.
- A click times out despite a visible control: Look for an overlay intercepting pointer events, an animation, a disabled state, or another element covering the target.
- Several elements match: Make the locator more specific rather than increasing the timeout. Ambiguous targeting can make the test act on the wrong control.
- An assertion times out: Confirm the expected text or state is correct and that the action actually triggers it. Increase the scoped timeout only when slow completion is legitimate.
- A load-state wait hangs or is misleading: Remove broad
networkidlesynchronization and assert the URL or application content that matters. - A list is intermittently incomplete: Wait for a stable count or an explicit completion indicator before calling
all(). - A popup wait is missed: Create the
waitForEvent('popup')promise before clicking the control that opens it.
Or skip the browser setup
If your goal is a screenshot rather than an interactive browser test, ScreenshotNeo can return a screenshot or PDF from one GET request. Cookie banners are accepted and removed before the shot, along with supported newsletter popups and chat widgets; those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with the response indicating the page verdict and billing status. Its MCP server provides screenshot tools for AI agents, and the free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
cURL example, using the documented API parameters:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for request options and response details. Sign up free for 1,000 screenshots a month, with no card required.
Frequently Asked Questions
Can I use waitForTimeout while debugging a Playwright test?
Yes. It can help briefly inspect a page during debugging, but replace it with a condition-based wait before relying on the test.
What is the default state for locator.waitFor()?
The default is visible. You can also specify attached, detached, or hidden.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →How long do Playwright web-first assertions wait by default?
The documented default is 5 seconds; it can be configured globally or per assertion.
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.

