Call frame.waitForNavigation() on the frame expected to navigate, and arm it at the same time as the action that triggers navigation. This avoids missing a fast navigation:
const [response] = await Promise.all([
frame.waitForNavigation(),
frame.click('a.my-link'),
]);
The promise resolves with the main resource response, or null when there is no such response, as with some History API navigations. Puppeteer documents the method and this concurrent-wait pattern.
As an Amazon Associate I earn from qualifying purchases.
Choose the frame that will navigate
Puppeteer represents DOM frames, including <iframe> elements, with its Frame class. A page can contain nested frames. Use the frame whose document or URL is expected to change; a page-level navigation wait is for navigation of the main frame, not a substitute for waiting on a child frame.
const mainFrame = page.mainFrame();
const frames = mainFrame.childFrames();
for (const child of frames) {
console.log(child.url());
}
For a known iframe, obtain its frame from the page’s frame tree and verify you have the intended one before triggering the action. Frame attachment, navigation, and detachment events are dispatched on the parent page. See Puppeteer’s Frame reference.
#1 Best Overall
Wait for navigation without a race
Put the navigation wait and the click or other triggering action in the same Promise.all. Do not await the click first and start waiting afterward: the navigation may already have begun by then.
const [response] = await Promise.all([
frame.waitForNavigation({ waitUntil: 'domcontentloaded' }),
frame.click('a.my-link'),
]);
console.log(response ? response.url() : 'Navigation had no main-resource response');
Choose a lifecycle condition based on what the next step needs. domcontentloaded waits for the document’s DOM content to be parsed; it does not guarantee that application-specific asynchronous work or a particular interface element is ready. If the next operation depends on a specific UI state, wait for that state after navigation.
Rank #2
Puppeteer treats a URL change made with the History API as navigation. Consequently, a successful waitForNavigation() does not necessarily mean a new document was loaded, and its result can be null. The official method reference describes the navigation options and return value.
When to wait for an element instead
Navigation and readiness are different conditions. If the requirement is “continue when this element appears,” wait for the element rather than assuming navigation is the signal you need.
Frame-level selector wait
await frame.waitForSelector('.results-ready', { visible: true });
Frame.waitForSelector() can wait across navigations, making it suitable when the target element may appear in the frame’s next document. It throws if the element does not appear before the timeout. Its options include visible, hidden, signal, and timeout; the documented default timeout is 30,000 milliseconds and can be changed with Page.setDefaultTimeout(). Check the Frame.waitForSelector reference and WaitForSelectorOptions reference for the installed version.
Locator for an interaction
For selecting and interacting with an element, Puppeteer’s current interaction guidance recommends locators. Locator actions automatically wait for element presence and the appropriate state, which can remove the need for a separate low-level selector wait in common interaction flows. Use waitForSelector() when its explicit wait behavior is what the task requires. See the Page interactions guide.
Rank #4
Do not carry an element handle across navigation
A selector wait on Frame is different from ElementHandle.waitForSelector(): the latter is tied to the existing element context and does not work across navigation or after that element is detached. Start a fresh frame-level wait or reacquire the element after navigation. See the ElementHandle.waitForSelector reference.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Troubleshooting frame navigation waits
- The wait times out although the click worked: confirm that the clicked link navigates the same frame you passed to
waitForNavigation(). If the page updates content without navigation, wait for a selector or application-specific condition instead. - The wait misses a fast navigation: start it in
Promise.allwith the triggering action; do not start it after the click resolves. - The wait resolves but the element is missing: navigation completion is not proof that asynchronous application work has finished. Add a frame-level selector wait for the element the next step requires.
- The response is
null: this is an allowed result, including when the URL changes through the History API. Do not treat a null response alone as evidence that the wait failed. - A selector wait fails after a redirect or frame replacement: avoid relying on an old
ElementHandle; use the relevantFrameand reacquire the target after navigation. - Method options or types do not match examples: check the installed Puppeteer version. The current references consulted identify versions 25.9.0 for
Frame.waitForNavigation, 25.10.0 forFrame.waitForSelector, and 25.12.0 for Frame and interaction documentation; APIs may differ across installed releases.
Or skip the browser setup
If your goal is simply to capture a page rather than automate navigation inside its frame tree, ScreenshotNeo offers a one-request screenshot API:
Best Value
- Used Book in Good Condition
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 API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for free.
Frequently Asked Questions
Does a History API URL change count as navigation in Puppeteer?
Yes. Puppeteer’s Frame.waitForNavigation documentation treats History API URL changes as navigation.
What does waitForNavigation return?
It resolves to the main resource response or null, depending on the navigation.
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.




