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