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