How to Wait for a Timeout in Pyppeteer
Use Pyppeteer waits correctly: fixed delays, selectors, functions, navigation, timeouts, errors, and production patterns.
For a fixed delay, await a number of milliseconds:
await page.waitFor(1000) # wait for 1 second
Pyppeteer interprets a numeric page.waitFor() argument as milliseconds. This sleeps for the requested duration; it does not prove that a page, element, request, or animation is ready. The Pyppeteer 0.0.25 API reference documents this behavior and uses milliseconds for its timeout values. Read the API reference.
Choose the wait that matches what must happen
| Need | Use | What it guarantees |
|---|---|---|
| Pause for a known amount of time | await page.waitFor(1000) |
Only that 1,000 milliseconds elapsed |
| Wait for an element in the DOM | await page.waitForSelector('h1') |
The selector matched; it may still be hidden |
| Wait for a visible element | await page.waitForSelector('h1', {'visible': True}) |
The matching element is visible |
| Wait for an element to disappear | await page.waitForSelector('.spinner', {'hidden': True}) |
The selector is absent or hidden |
| Wait for XPath | await page.waitForXPath('//h1') |
An XPath match exists |
| Wait for a page condition | await page.waitForFunction('document.readyState === "complete"') |
The function returns a truthy value |
| Wait for a navigation | await page.waitForNavigation() |
A navigation event completed according to its wait options |
Condition-based waits express readiness more accurately than guessing a sleep duration. They can still time out when the condition never becomes true.
Fixed delays with page.waitFor()
Use a numeric delay when the pause itself is intentional, such as allowing a short animation or a deliberately scheduled script to run.
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
try:
page = await browser.newPage()
await page.goto('https://example.com')
await page.waitFor(1000)
print(await page.title())
finally:
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
The argument is milliseconds: 1000 is one second and 5000 is five seconds. A fixed delay does not account for a slow network, a fast cache, a failed request, or content that takes longer than the chosen value.
Wait for a selector
When the next operation needs an element, wait for that element instead of sleeping.
heading = await page.waitForSelector('h1', {'timeout': 5000})
text = await page.evaluate('(element) => element.textContent', heading)
print(text)
waitForSelector() returns immediately if the selector is already present. By default it waits for DOM presence, not visibility. Add visible: True when a hidden node is not sufficient, or hidden: True when you need a loading element to disappear.
await page.waitForSelector('#results', {'visible': True, 'timeout': 10000})
await page.waitForSelector('.loading', {'hidden': True, 'timeout': 10000})
The documented default timeout for waitForSelector is 30,000 milliseconds. Passing 0 disables that timeout in the cited Pyppeteer reference. Check the versioned reference.
Wait for XPath
Use XPath when a CSS selector cannot express the relationship you need.
matches = await page.waitForXPath('//button[contains(normalize-space(), "Continue")]', {'timeout': 5000})
print(len(matches))
waitForXPath() follows the same timeout model documented for selector waits, including the 30-second default and 0 to disable it.
Wait for a JavaScript condition
waitForFunction() polls a browser-side expression until it returns a truthy value. The documented polling modes include raf, mutation, and a numeric interval in milliseconds.
await page.waitForFunction(
'document.readyState === "complete"',
{'timeout': 10000, 'polling': 'raf'}
)
await page.waitForFunction(
'document.querySelectorAll(".product").length >= 10',
{'timeout': 15000, 'polling': 250}
)
Use a condition that represents the content your code needs. For example, checking for ten rendered products is more useful than waiting two seconds when product data arrives at an unpredictable time.
Wait for navigation without a race
If a click or form submission causes navigation, coordinate the action and navigation wait with asyncio.gather. Starting the navigation wait too late can miss the event.
await asyncio.gather(
page.waitForNavigation(),
page.click('a.next'),
)
The API reference explicitly documents this coordination pattern and warns about the race created by starting the waits separately. Set navigation options when the page may remain active after the first response:
await asyncio.gather(
page.waitForNavigation({'waitUntil': 'networkidle0', 'timeout': 30000}),
page.click('a.next'),
)
Verify the exact option support in the Pyppeteer version installed in your project. The project is an unofficial Python port of Puppeteer, and its documentation notes differences caused by Python and JavaScript APIs. See the project README.
Timeout units, defaults, and scope
- Pyppeteer timeout values are milliseconds.
- The documented default is 30,000 milliseconds for
waitForSelector,waitForXPath,waitForFunction,waitForRequest, andwaitForResponse. - The reference also documents a 30,000-millisecond default for navigation calls such as
goto()andwaitForNavigation(). - Passing
0disables the documented timeout for the relevant wait. page.waitFor(1000)requests a one-second delay;{'timeout': 1000}gives a condition wait at most one second to succeed. They are different controls.
For navigation methods, setDefaultNavigationTimeout() changes the default navigation timeout:
page.setDefaultNavigationTimeout(60000)
await page.goto('https://example.com')
Pyppeteer accepts an options dictionary and, depending on the call style and version, keyword arguments such as timeout=5000. Confirm the signature in the installed-version documentation.
Complete example
import asyncio
from pyppeteer import launch
async def main():
browser = await launch()
try:
page = await browser.newPage()
await page.goto('https://example.com', {'timeout': 30000})
# Wait for the heading the next step needs.
heading = await page.waitForSelector('h1', {'visible': True, 'timeout': 5000})
text = await page.evaluate('(element) => element.textContent', heading)
print(text.strip())
# A fixed delay is separate from the selector wait.
await page.waitFor(250)
finally:
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
The Pyppeteer README documents the launch, new-page, and navigation pattern. It also notes that Chromium may be downloaded on first run if a browser is not already available. Read the repository documentation.
Or skip the browser setup
If your goal is a screenshot rather than browser automation, ScreenshotNeo provides a single HTTP request for a PNG, JPEG, WebP, or PDF. Its capture options include waiting for a selector, a delay, or network idle, so you can express the readiness condition without managing Chromium. See the ScreenshotNeo API 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}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. 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.
Troubleshooting
“TimeoutError” from a selector or function wait
Cause: the selector never matched, the element stayed hidden, the page script failed, or the timeout was too short.
Fix: inspect the selector, use visible: True only when visibility is required, wait for a more stable parent or application state, and log the page URL and console errors. Increase the timeout only when the condition is valid but slow.
The wait returns before content is usable
Cause: DOM presence is not the same as visibility, layout completion, or data readiness.
Fix: use visible: True, wait for a specific text or count with waitForFunction(), or wait for the request that supplies the data.
A fixed delay is flaky
Cause: a guessed duration is shorter than some runs and unnecessarily long in others.
Fix: replace the sleep with a selector, function, request, or navigation wait that represents the actual condition.
Navigation wait hangs after a click
Cause: the click did not navigate, navigation happened before the wait started, or the site uses client-side routing.
Fix: use asyncio.gather for actions that navigate; for single-page applications, wait for the route-specific selector or URL condition instead.
Chromium cannot start
Cause: Chromium is unavailable, the first-run download did not complete, or the runtime lacks required launch dependencies.
Fix: follow the Pyppeteer repository setup instructions, provide the browser executable path when appropriate, and capture the launch exception before retrying.
The code works with modern Puppeteer but not Pyppeteer
Cause: Pyppeteer 0.0.25 documentation is from 2018 and is an unofficial port; current Puppeteer methods are not proof of Pyppeteer support.
Fix: check the installed Pyppeteer package and its versioned reference before copying an upstream example. See the documentation history.
Performance, reliability, and cost notes
- Fixed sleeps add their full duration to every run, even when the page is ready sooner.
- Condition waits can finish early, but choose selectors and predicates that are stable across responsive layouts and content variants.
- Use explicit timeouts so a failed condition does not hold a worker indefinitely.
- Always close the browser in a
finallyblock to avoid leaking processes. - For navigation-triggering actions, coordinate the action and wait to avoid missed events.
- A timeout of
0removes the safety limit documented by Pyppeteer; use it only when an external cancellation or job deadline exists. - Pyppeteer itself requires browser process management and may download Chromium on first run. ScreenshotNeo moves that setup to an HTTP API and charges only for clean shots; failed loads and cache hits are not billed.
FAQ
Is page.waitFor(1000) one second?
Yes. Pyppeteer interprets the numeric argument as milliseconds, so 1,000 means one second.
Does waitForSelector() wait for visibility?
No. The default is DOM presence. Pass visible: True when visibility matters.
What does a timeout of zero mean?
The Pyppeteer 0.0.25 reference documents 0 as disabling the timeout for the relevant wait.
Should I use current Puppeteer documentation?
Use it as background only. Verify every method and option against the Pyppeteer version installed in your project.
When should I use a fixed delay?
Use one when the pause itself is intentional. If you are waiting for readiness, prefer a selector, function, request, or navigation condition.


