Playwright: waitForSelector: waiting for locator(...) to be visible
Last verified
TimeoutError: page.waitForSelector: Timeout 30000ms exceeded.
Call log:
- waiting for locator('.results') to be visibleWhat 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
- Look at a trace snapshot at the moment of timeout to see what the page actually showed.
- If the element is legitimately hidden, wait with state: 'attached' instead of the default visible.
- For iframes, wait on page.frameLocator('iframe').locator('.results').
- Check the network panel in the trace for a failed API request and handle that path explicitly.
- 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
- PlaywrightPlaywright is Microsoft's open-source browser automation framework that provides a single API to control Chrom
- Chrome DevTools Protocol (CDP)The Chrome DevTools Protocol (CDP) is the JSON-over-WebSocket interface Chrome exposes for inspecting and cont
- CSS SelectorA CSS selector is a pattern used to identify and target specific HTML elements on a web page, widely used in b
- Headless ChromeHeadless Chrome is Google Chrome running without a visible window. It renders pages and runs JavaScript like n
- IframeAn iframe (inline frame) is an HTML element that embeds a separate web page within a parent page, creating an
- Web ElementA web element is any individual component rendered on a web page — buttons, links, input fields, images, text
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.