# 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 ,