Skip to content
Playwright · error message

Playwright: waitForSelector: waiting for locator(...) to be visible

RetryableLocators & waiting

Last verified

The exact message
TimeoutError: page.waitForSelector: Timeout 30000ms exceeded. Call log: - waiting for locator('.results') to be visible

What it means

A wait for an element to become visible ran out of time. page.waitForSelector() and locator.waitFor() default to the visible state, and the call log line waiting for locator('...') to be visible records exactly what was awaited. When the log has nothing after that line, no matching element ever appeared. When it shows locator resolved to N elements or a hidden element, something matched but stayed invisible.

Visible in Playwright means a non-empty bounding box and no visibility:hidden. Elements with zero size, display:none, or in a collapsed parent count as hidden even if they are in the DOM. Elements with opacity:0 count as visible.

What is not wrong: waiting itself. The question is whether the element should have appeared, and why it did not.

What it looks like

// JavaScript
await page.locator('.results').waitFor();                     // visible
await page.locator('input[name=csrf]').waitFor({ state: 'attached' });

# Python
page.wait_for_selector(".results")                           # visible
page.frame_locator("#embed").locator(".results").wait_for()

Why it happens

  • The selector never matches: content loaded under a different class, or text changed.
  • The element is rendered but hidden (zero size, display:none) until another step runs.
  • The content lives inside an iframe and the wait was issued on the top page.
  • The data request behind the element failed, so an empty state or error message rendered instead.
  • You wanted presence, not visibility, for example a hidden input or a script tag.

How to fix it

  1. Look at a trace snapshot at the moment of timeout to see what the page actually showed.
  2. If the element is legitimately hidden, wait with state: 'attached' instead of the default visible.
  3. For iframes, wait on page.frameLocator('iframe').locator('.results').
  4. Check the network panel in the trace for a failed API request and handle that path explicitly.
  5. In tests, prefer await expect(locator).toBeVisible(), which auto-retries and gives a clearer message.

How to stop it recurring

Wait for the thing your next step depends on, in the state it needs. Use attached for hidden inputs and visible for things a user would see. Handle empty and error states explicitly so a failed backend call produces a clear assertion failure instead of a generic 30-second wait.

Official documentation: playwright.dev/docs/api/class-locator#locator-wait-for. See all Playwright error messages.

Related errors

Definitions

Quick answers

What does "waitForSelector: waiting for locator(...) to be visible" mean in Playwright?

A wait for an element to become visible ran out of time. page.waitForSelector() and locator.waitFor() default to the visible state, and the call log line waiting for locator('...') to be visible records exactly what was awaited. When the log has nothing after that line, no matching element ever appeared. When it shows locator resolved to N elements or a hidden element, something matched but stayed invisible.

How do I fix "waitForSelector: waiting for locator(...) to be visible"?

1. Look at a trace snapshot at the moment of timeout to see what the page actually showed. 2. If the element is legitimately hidden, wait with state: 'attached' instead of the default visible. 3. For iframes, wait on page.frameLocator('iframe').locator('.results'). 4. Check the network panel in the trace for a failed API request and handle that path explicitly. 5. In tests, prefer await expect(locator).toBeVisible(), which auto-retries and gives a clearer message.

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.