# Playwright Error Index Quick-reference for specific Playwright error messages. Find your error, understand the cause, apply the fix. > **How to use**: Search this file for the exact error text you see in your terminal or test report. Each entry gives you the cause and a working fix. --- ## Locator & Element Errors --- ### "locator.click: Target closed" **Cause**: The page or frame navigated away (or was closed) before Playwright finished executing the action on the element. **Common triggers**: - Clicking a link that triggers navigation while another action is still pending - A form submit causes a page reload before a subsequent `click()` or `fill()` resolves - Calling `page.close()` or `context.close()` in a `finally` block that races with an in-flight action - An unhandled exception in the application triggers an error page redirect **Fix**: Wait for navigation to complete before performing the next action, or use `Promise.all` to coordinate the click and navigation together. ```typescript // TypeScript — wait for navigation caused by clicking a link await Promise.all([ page.waitForURL("**/dashboard"), page.getByRole("link", { name: "Dashboard" }).click(), ]) ``` ```javascript // JavaScript — same pattern await Promise.all([ page.waitForURL("**/dashboard"), page.getByRole("link", { name: "Dashboard" }).click(), ]) ``` If the page is closing intentionally (e.g., a popup), capture a reference before the action: ```typescript // TypeScript — handle popup that closes itself const popupPromise = page.waitForEvent("popup") await page.getByRole("button", { name: "Open popup" }).click() const popup = await popupPromise await popup.waitForLoadState() // interact with popup before it closes await popup.getByRole("button", { name: "Confirm" }).click() ``` ```javascript // JavaScript — handle popup that closes itself const popupPromise = page.waitForEvent("popup") await page.getByRole("button", { name: "Open popup" }).click() const popup = await popupPromise await popup.waitForLoadState() await popup.getByRole("button", { name: "Confirm" }).click() ``` **Related**: [core/assertions-and-waiting.md](assertions-and-waiting.md), [core/multi-context-and-popups.md](multi-context-and-popups.md) --- ### "waiting for locator('...') to be visible" **Cause**: Playwright's auto-waiting timed out because the element never appeared in the DOM or remained hidden (e.g., `display: none`, `visibility: hidden`, zero size, or behind `aria-hidden`). **Common triggers**: - The element renders conditionally and the condition was not met (missing data, unauthenticated state) - Content loads asynchronously and the locator was evaluated before the API response arrived - The selector is wrong — typo in role, name, or test ID - The element exists but is off-screen or inside a collapsed container - CSS animation or transition keeps the element at `opacity: 0` without making it visible to Playwright **Fix**: First confirm the locator matches what you expect using the Playwright Inspector. Then ensure the precondition for the element to appear is met. ```typescript // TypeScript — debug: check what the locator resolves to console.log(await page.getByRole("button", { name: "Submit" }).count()) // If 0, the element is not in the DOM — check your selector or page state // Correct approach: wait for the data to load first await page.waitForResponse((resp) => resp.url().includes("/api/data") && resp.status() === 200) await expect(page.getByRole("button", { name: "Submit" })).toBeVisible() ``` ```javascript // JavaScript — same approach console.log(await page.getByRole("button", { name: "Submit" }).count()) await page.waitForResponse((resp) => resp.url().includes("/api/data") && resp.status() === 200) await expect(page.getByRole("button", { name: "Submit" })).toBeVisible() ``` **Related**: [core/locators.md](locators.md), [core/assertions-and-waiting.md](assertions-and-waiting.md) --- ### "locator.click: Error: strict mode violation" **Cause**: The locator matched more than one element and Playwright's strict mode (enabled by default) refuses to act on ambiguous matches. **Common triggers**: - Using `getByRole('button')` when multiple buttons exist on the page - Using `getByText('Save')` when both "Save" and "Save as draft" are present - Partial text matching when `exact: true` is needed - The same component is rendered multiple times (e.g., in a list) **Fix**: Make the locator more specific so it resolves to exactly one element. ```typescript // TypeScript // BAD — matches multiple buttons await page.getByRole("button", { name: "Save" }).click() // GOOD — use exact matching await page.getByRole("button", { name: "Save", exact: true }).click() // GOOD — scope to a specific container await page.getByRole("dialog").getByRole("button", { name: "Save" }).click() // GOOD — use .first(), .last(), .nth() when order is meaningful await page.getByRole("listitem").first().click() // GOOD — chain with filter for list items await page .getByRole("listitem") .filter({ hasText: "Project Alpha" }) .getByRole("button", { name: "Delete" }) .click() ``` ```javascript // JavaScript // BAD — matches multiple buttons await page.getByRole("button", { name: "Save" }).click() // GOOD — use exact matching await page.getByRole("button", { name: "Save", exact: true }).click() // GOOD — scope to a specific container await page.getByRole("dialog").getByRole("button", { name: "Save" }).click() // GOOD — use .first(), .last(), .nth() when order is meaningful await page.getByRole("listitem").first().click() // GOOD — chain with filter for list items await page .getByRole("listitem") .filter({ hasText: "Project Alpha" }) .getByRole("button", { name: "Delete" }) .click() ``` **Related**: [core/locators.md](locators.md), [core/locator-strategy.md](locator-strategy.md) --- ### "Error: expect(locator).toBeVisible() — locator resolved to X elements" **Cause**: Same root cause as strict mode violation above — the assertion's locator matched multiple elements, so Playwright cannot determine which one to assert on. **Common triggers**: - Same as strict mode violation triggers above - Writing `expect(page.locator('.item')).toBeVisible()` when there are many `.item` elements **Fix**: Narrow the locator to a single element (see strict mode violation fix above). Alternatively, if you intentionally want to check all matches: ```typescript // TypeScript — assert count instead of visibility await expect(page.getByRole("listitem")).toHaveCount(5) // Or assert on a specific one await expect(page.getByRole("listitem").first()).toBeVisible() // Or assert all are visible with a loop for (const item of await page.getByRole("listitem").all()) { await expect(item).toBeVisible() } ``` ```javascript // JavaScript — assert count instead of visibility await expect(page.getByRole("listitem")).toHaveCount(5) // Or assert on a specific one await expect(page.getByRole("listitem").first()).toBeVisible() // Or assert all are visible with a loop for (const item of await page.getByRole("listitem").all()) { await expect(item).toBeVisible() } ``` **Related**: [core/locators.md](locators.md), [core/assertions-and-waiting.md](assertions-and-waiting.md) --- ### "locator.fill: Error: Element is not an ,