Skip to content
Puppeteer · error message

Puppeteer: TimeoutError: Waiting for selector failed

RetryableSelectors & waiting

Last verified

The exact message
TimeoutError: Waiting for selector `.results` failed [cause]: TimeoutError: Waiting failed: 30000ms exceeded

What it means

page.waitForSelector() (or frame.waitForSelector()) polled for the selector for its whole timeout, 30 seconds by default, and the condition was never met. In current Puppeteer the timeout detail is attached as the error's cause; older releases printed it on the same line, for example Waiting for selector `.results` failed: Waiting failed: 30000ms exceeded.

By default the method only waits for the element to exist in the DOM. With { visible: true } it also requires a non-empty bounding box and no visibility: hidden; with { hidden: true } it waits for the opposite. So the same selector can succeed or time out depending on those options.

What is not wrong: the timeout length is rarely the problem. The selector either does not match what the page renders, is looked for in the wrong frame, or matches an element that never becomes visible.

What it looks like

try {
  await page.waitForSelector('.results', { visible: true, timeout: 15000 });
} catch (err) {
  await page.screenshot({ path: 'failure.png', fullPage: true });
  console.error(err.message, err.cause?.message);
  throw err;
}

// Locator API (Puppeteer 21+): waits, then clicks
await page.locator('button.load-more').click();

Why it happens

  • The selector does not match the rendered markup: generated class names, changed IDs, or text that differs from what you expected.
  • The element is inside an iframe or a shadow root that page-level CSS selectors do not reach.
  • visible: true was requested but the element stays hidden until another interaction.
  • The site served a different page (consent wall, login redirect, bot challenge, empty search results).
  • The content loads from an API call that failed or was blocked by request interception.

How to fix it

  1. Take a screenshot and dump page.content() at the moment of failure to see what was actually rendered.
  2. Test the selector in DevTools on the same page state with document.querySelectorAll().
  3. For iframes, get the frame (await (await page.$('iframe')).contentFrame()) and wait inside it.
  4. Use Puppeteer's shadow-piercing selector syntax (>>> or the pierce/ prefix) for web components.
  5. Drop visible: true if you only need the element to exist, or perform the step that reveals it.
  6. Consider page.locator(selector).click(), which waits for the element to be visible, enabled and stable before acting.

How to stop it recurring

Prefer stable attributes (data-testid, name, ARIA roles via ::-p-aria()) over generated class names. Capture a screenshot and HTML on every timeout so failures are diagnosable without rerunning. Handle consent walls and empty states explicitly so they produce clear errors instead of 30-second waits.

Official documentation: pptr.dev/api/puppeteer.page.waitforselector. See all Puppeteer error messages.

Related errors

Definitions

Quick answers

What does "TimeoutError: Waiting for selector failed" mean in Puppeteer?

page.waitForSelector() (or frame.waitForSelector() ) polled for the selector for its whole timeout, 30 seconds by default, and the condition was never met. In current Puppeteer the timeout detail is attached as the error's cause ; older releases printed it on the same line, for example Waiting for selector `.results` failed: Waiting failed: 30000ms exceeded .

How do I fix "TimeoutError: Waiting for selector failed"?

1. Take a screenshot and dump page.content() at the moment of failure to see what was actually rendered. 2. Test the selector in DevTools on the same page state with document.querySelectorAll(). 3. For iframes, get the frame (await (await page.$('iframe')).contentFrame()) and wait inside it. 4. Use Puppeteer's shadow-piercing selector syntax (>>> or the pierce/ prefix) for web components. 5. Drop visible: true if you only need the element to exist, or perform the step that reveals it. 6. Consider page.locator(selector).click(), which waits for the element to be visible, enabled and stable…

Run browser automation you can watch and take over

Autonoly runs each job in a real cloud Chromium browser driven by Playwright. Timeouts and detached elements still happen; the difference is that you can watch the run live, see the step that failed, and take control of the browser to finish it.

Also see Live browser control.