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.
Use page.waitForFunction() when a custom condition concerns page-wide state, and locator.waitForFunction() when it belongs to a particular element. Both repeatedly evaluate a predicate until it returns a truthy value. They can receive an argument and a timeout; in the JavaScript API their documented default timeout is 0 (no timeout), so a finite timeout is usually safer in CI.
For ordinary UI readiness, prefer locator actions and web-first assertions. Playwright already auto-waits for actionability and retries assertions. A fixed page.waitForTimeout() delay should be limited to debugging because time-based tests are inherently flaky.
Choose the right wait
| API | Scope | Best use | Retry behavior |
|---|---|---|---|
page.waitForFunction() |
Entire page | A browser variable, document flag, computed value, or other global condition | Evaluates the predicate in the page context until its result is truthy |
locator.waitForFunction() |
One locator | A custom condition attached to a specific element | Re-resolves the locator on every retry, so it tolerates re-rendering |
expect(locator).toHaveText() and other web-first assertions |
Element or page outcome | A user-visible test result such as status text or visibility | Retries until the assertion timeout and gives assertion-focused diagnostics |
locator.waitFor() |
One locator | Known state: attached, detached, visible, or hidden |
Waits for the selected locator state; visible is the default |
Use a function wait only when the condition does not map cleanly to a locator state or assertion. Playwright describes locators as the central part of its auto-waiting and retry-ability, and most explicit waits are unnecessary before an action.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteWait for page-level state with page.waitForFunction()
The JavaScript and TypeScript signature is:
await page.waitForFunction(predicate, arg?, options?);
The predicate runs in the browser (page) context, not in Node.js. The call resolves when the return value is truthy. In the JavaScript API it returns a JSHandle, which you can read or dispose of when you need the value itself.
#1 Best Overall
Basic example
import { test, expect } from '@playwright/test';
test('waits for a page flag', async ({ page }) => {
await page.goto('https://example.com');
await page.waitForFunction(() => window.innerWidth < 100);
});
A more realistic condition might be an application flag set after initialization:
await page.waitForFunction(() => window.__appReady === true);
Only use globals that the page actually exposes. A typo or an exception in the predicate causes the wait to fail rather than silently succeeding.
Pass an argument safely
The second parameter is serialized and supplied to the predicate in the page context. This keeps data out of interpolated JavaScript strings.
const selector = '.foo';
await page.waitForFunction(
sel => Boolean(document.querySelector(sel)),
selector
);
You can pass serializable strings, numbers, booleans, arrays, and objects. The predicate still executes in the browser, so Node-only objects and functions cannot be used directly as page values.
Use a finite timeout
await page.waitForFunction(
() => window.__appReady === true,
undefined,
{ timeout: 15_000 }
);
In JavaScript, both function-wait methods document timeout: 0 by default, meaning no timeout. That can leave a hung test indefinitely. Set a per-call timeout, or establish a project default:
page.setDefaultTimeout(15_000);
// Or for every page in a context:
browserContext.setDefaultTimeout(15_000);
A per-call option is explicit and easy to tune for a slow operation. A default protects waits you add later. Choose a value based on the operation and CI environment rather than hiding a race with an arbitrary large number.
Rank #2
Wait for an element with locator.waitForFunction()
Use the locator form when the predicate is about one element. It was added in Playwright v1.62.
const toggle = page.getByRole('button', { name: 'Menu' });
await toggle.click();
await toggle.waitForFunction(element =>
element.hasAttribute('aria-expanded')
);
The locator is re-resolved on each retry. That matters in React, Vue, and other applications that replace DOM nodes during rendering: you do not wait on a stale, one-time element handle.
Pass an element-scoped argument
await page.getByTestId('status').waitForFunction(
(element, value) => element.textContent === value,
'Ready'
);
The first predicate parameter is the current element; the next parameter is your serialized argument. Keep the condition focused and deterministic. If the locator matches multiple elements, make it unique or narrow it first, so the wait expresses one intended target.
Prefer an assertion for visible outcomes
await expect(page.getByRole('status')).toHaveText('Ready');
This is clearer when the test requirement is “the user sees Ready.” Assertions retry automatically and report the expected and received values. Reserve locator.waitForFunction() for custom browser-side logic, such as checking a computed property or a DOM relationship that has no suitable assertion.
Predicates, promises, and cancellation
Truthy means success
The wait completes on any truthy result: true, a non-empty string, a non-zero number, or an object. Return an explicit boolean when possible; it makes the intent obvious.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
await page.waitForFunction(() => {
const progress = document.querySelector('#progress');
return progress?.getAttribute('aria-valuenow') === '100';
});
Async predicates
If the predicate returns a Promise, Playwright waits for that Promise and then checks its resolved value.
await page.waitForFunction(async () => {
const response = await fetch('/health');
return response.ok;
});
Use this sparingly. Polling network endpoints from a page predicate can add load and obscure the real synchronization point; waiting for the application’s rendered state or a network event may be clearer.
Thrown and rejected predicates
If the predicate throws synchronously or its Promise rejects, the wait throws. Guard optional DOM access and make expected “not ready yet” states return false instead of throwing.
await page.waitForFunction(() => {
const node = document.querySelector('[data-state]');
return node ? node.getAttribute('data-state') === 'done' : false;
});
Abort a wait
Current APIs accept an AbortSignal. Aborting causes the operation to throw; it does not turn off the configured timeout.
const controller = new AbortController();
const wait = page.waitForFunction(
() => window.__jobFinished === true,
undefined,
{ timeout: 30_000, signal: controller.signal }
);
// Cancel from another branch when the job is no longer relevant:
controller.abort();
await wait;
Handle the resulting error when cancellation is an expected branch, and preserve the original error when it indicates a real test failure.
Why page.waitForTimeout() is flaky
A fixed delay guesses how long work will take. If the page is slower than the guess, the test races and fails; if it is faster, the test wastes time. A delay also says nothing about whether the desired state was reached. Playwright’s guidance is: “Never wait for timeout in production. Tests that wait for time are inherently flaky.”
Replace this:
await page.click('#save');
await page.waitForTimeout(1000);
expect(await page.locator('#status').textContent()).toBe('Saved');
with a state-based wait:
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('status')).toHaveText('Saved');
For a custom condition, use a function wait with a finite timeout instead of sleeping.
Rank #4
Known locator states and discouraged selector waits
When all you need is attachment or visibility, use:
Recommended Free Tools
await page.locator('#order-sent').waitFor({ state: 'visible' });
The supported states are attached, detached, visible, and hidden. For new code, prefer locator methods and assertions over page.waitForSelector(); the page-level selector API is discouraged because locator APIs provide better retry behavior and diagnostics.
Troubleshooting function waits
Timeout despite the condition appearing true
- Confirm the predicate runs in the page context. Node variables are not automatically available there; pass serializable values as
arg. - Check the exact type and value. A string such as
'false'is truthy, while the booleanfalseis not. - Verify the page, frame, and origin. The condition may exist in an iframe rather than the main document.
- Capture diagnostics at failure: URL, console messages, and a screenshot or trace. A finite timeout turns an infinite hang into an actionable failure.
“Element is not defined” or stale-element behavior
Use a locator rather than an element handle, and use locator.waitForFunction() when the element can be replaced during rendering. The locator will be resolved again on each retry.
The predicate throws immediately
Protect missing nodes with optional chaining or an explicit null check. Return false for “not ready yet”; throw only for a condition that should fail the test immediately.
The wait never ends
Remember that the JavaScript default is no timeout. Set { timeout: ... } or a context/page default. If the condition is optional, use an abort signal or redesign the test so it waits for a bounded application event.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11An assertion is clearer
Replace a custom text or visibility predicate with a web-first assertion whenever possible. It communicates the user-visible contract and usually produces a better failure message.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and reliability practices
- Prefer a specific locator or page flag over scanning the whole DOM repeatedly.
- Use the smallest predicate that answers the question; avoid expensive layout reads and repeated network requests.
- Set realistic finite timeouts and keep the condition deterministic.
- Synchronize on outcomes, not implementation timing. Actions already wait for actionability.
- Use traces, screenshots, and console/network logging to diagnose a genuine race instead of adding a longer sleep.
- Keep browser-side predicates serializable and independent of Node modules.
Or skip the browser setup
If your goal is to obtain a clean screenshot after a page reaches its state, ScreenshotNeo provides a website screenshot API and MCP server. It is separate from Playwright’s test synchronization, but can remove the browser-capture plumbing from scripts and AI workflows. A GET request returns PNG, JPEG, WebP, or PDF; options include waiting for a selector, a delay, or network idle, custom JavaScript, clicking before capture, full-page lazy-image loading, device presets, dark mode, and more.
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}`);
See the ScreenshotNeo documentation for request options. Cookie and consent banners are accepted and 60-plus known consent platforms, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with the result reported in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Complete decision checklist
- Is the requirement a normal click, fill, navigation, visibility, or text outcome? Use a locator action or web-first assertion.
- Is it a known locator state? Use
locator.waitFor(). - Is it custom page-wide browser logic? Use
page.waitForFunction(). - Is it custom logic tied to a re-rendering element? Use
locator.waitForFunction(). - Could the predicate hang? Supply a finite timeout and, when appropriate, an
AbortSignal. - Are you about to add a fixed sleep? Replace it with the state or predicate that proves readiness.
Frequently Asked Questions
What does Playwright return from waitForFunction?
The JavaScript API returns a JSHandle for the predicate’s result. Most tests only need to await completion; read the handle only when the value itself is required.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Can I use waitForFunction inside an iframe?
Run the wait on the relevant Frame object rather than the main Page, so the predicate executes in the document that owns the condition.
Is locator.waitForFunction available in older Playwright versions?
It was added in Playwright v1.62. Upgrade if your installed version does not expose the method.
Should I increase the timeout when a wait fails intermittently?
First verify the predicate, frame, locator, and application state. Increase the timeout only when the operation’s legitimate worst-case duration requires it; do not use a larger value to mask an incorrect 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.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →

