Files
claude-code-guide/skills/playwright/core/error-index.md
T

58 KiB

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 — wait for navigation caused by clicking a link
await Promise.all([
  page.waitForURL("**/dashboard"),
  page.getByRole("link", { name: "Dashboard" }).click(),
])
// 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 — 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 — 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, core/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 — 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 — 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, core/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

// 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

// 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, core/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 — 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 — 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, core/assertions-and-waiting.md


"locator.fill: Error: Element is not an , or [contenteditable] element"</h3> <p dir="auto"><strong>Cause</strong>: <code>fill()</code> only works on <code><input></code>, <code><textarea></code>, or elements with <code>contenteditable</code>. The locator resolved to a different element type (e.g., a <code><div></code>, <code><label></code>, or the <code><form></code> itself).</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Locating by label text but the label and input are not properly associated via <code>for</code>/<code>id</code></li> <li>Using <code>getByText()</code> instead of <code>getByLabel()</code> or <code>getByRole('textbox')</code></li> <li>Custom components that wrap the actual <code><input></code> in a styled <code><div></code></li> <li>Rich text editors that use <code>contenteditable</code> on an inner element, not the outer wrapper</li> </ul> <p dir="auto"><strong>Fix</strong>: Target the actual input element.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — targets the label, not the input </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByText</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// GOOD — getByLabel finds the associated input </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// GOOD — target by role </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"textbox"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Email"</span> <span class="p">}).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// For contenteditable rich text editors </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">'[contenteditable="true"]'</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"Hello world"</span><span class="p">)</span> <span class="c1">// If fill() still doesn't work (e.g., custom widget), use keyboard input </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"textbox"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Email"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">keyboard</span><span class="p">.</span><span class="kr">type</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// BAD — targets the label, not the input </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByText</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// GOOD — getByLabel finds the associated input </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// GOOD — target by role </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"textbox"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Email"</span> <span class="p">}).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="c1">// For contenteditable rich text editors </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">'[contenteditable="true"]'</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"Hello world"</span><span class="p">)</span> <span class="c1">// If fill() still doesn't work (e.g., custom widget), use keyboard input </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"textbox"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Email"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">keyboard</span><span class="p">.</span><span class="nx">type</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="locators.md">core/locators.md</a>, <a href="forms-and-validation.md">core/forms-and-validation.md</a></p> <hr> <h3>"Error: elementHandle.click: Node is not an Element"</h3> <p dir="auto"><strong>Cause</strong>: You are using the legacy ElementHandle API and the handle has gone stale — the underlying DOM node was removed or replaced by a re-render.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Storing an <code>elementHandle</code> in a variable and using it after a navigation or React/Vue re-render</li> <li>Using <code>page.$()</code> or <code>page.$$()</code> instead of Locator API</li> <li>Framework hydration replaces the server-rendered node with a client-rendered one</li> </ul> <p dir="auto"><strong>Fix</strong>: Switch from ElementHandle to Locator. Locators re-query the DOM on every action and never go stale.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — stale handle </span><span class="kr">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$</span><span class="p">(</span><span class="s2">"button.submit"</span><span class="p">)</span> <span class="c1">// ... page re-renders ... </span><span class="k">await</span> <span class="nx">button</span><span class="o">!</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Node is not an Element </span> <span class="c1">// GOOD — locator always re-queries </span><span class="kr">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">})</span> <span class="c1">// ... page re-renders ... </span><span class="k">await</span> <span class="nx">button</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// works fine </span></code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// BAD — stale handle </span><span class="kr">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$</span><span class="p">(</span><span class="s2">"button.submit"</span><span class="p">)</span> <span class="c1">// ... page re-renders ... </span><span class="kr">await</span> <span class="nx">button</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Node is not an Element </span> <span class="c1">// GOOD — locator always re-queries </span><span class="kr">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">})</span> <span class="c1">// ... page re-renders ... </span><span class="kr">await</span> <span class="nx">button</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// works fine </span></code></pre></div><p dir="auto"><strong>Related</strong>: <a href="locators.md">core/locators.md</a></p> <hr> <h3>"Error: locator.click: Timeout 30000ms exceeded"</h3> <p dir="auto"><strong>Cause</strong>: The element was found in the DOM but was not actionable within the timeout. "Actionable" means visible, stable (not animating), enabled, and not obscured by another element.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>An overlay, modal, or toast is covering the element</li> <li>The element is disabled (<code>disabled</code> attribute or <code>aria-disabled="true"</code>)</li> <li>CSS animation has not completed (element is still moving)</li> <li>A loading spinner overlays the button</li> <li>The element is outside the viewport and Playwright's auto-scroll failed</li> </ul> <p dir="auto"><strong>Fix</strong>: Identify what is blocking the action using traces, then address it.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// Step 1: Enable tracing to diagnose </span><span class="c1">// In playwright.config.ts: </span><span class="c1">// use: { trace: 'on-first-retry' } </span><span class="c1">// Then run: npx playwright show-trace trace.zip </span> <span class="c1">// Common fix: wait for the overlay to disappear </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".loading-overlay"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Common fix: force click when you know the overlay is benign </span><span class="c1">// (use sparingly — this skips actionability checks) </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">({</span> <span class="nx">force</span>: <span class="kt">true</span> <span class="p">})</span> <span class="c1">// Common fix: wait for the element to be enabled </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">})).</span><span class="nx">toBeEnabled</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// Common fix: wait for the overlay to disappear </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".loading-overlay"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Common fix: force click (use sparingly) </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">({</span> <span class="nx">force</span><span class="o">:</span> <span class="kc">true</span> <span class="p">})</span> <span class="c1">// Common fix: wait for the element to be enabled </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">})).</span><span class="nx">toBeEnabled</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Submit"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a>, <a href="debugging.md">core/debugging.md</a></p> <hr> <h2>Navigation & Page Errors</h2> <hr> <h3>"page.goto: net::ERR_CONNECTION_REFUSED"</h3> <p dir="auto"><strong>Cause</strong>: Playwright could not connect to the target URL. The server is not running or is not listening on the expected host/port.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Forgot to start the dev server before running tests</li> <li>Dev server is on a different port than <code>baseURL</code> in config</li> <li>Server is listening on <code>127.0.0.1</code> but test uses <code>localhost</code> (or vice versa) and the OS resolves them differently</li> <li>In CI, the application build/start step failed silently</li> <li>Docker container networking: the app runs inside a container but the test tries to reach <code>localhost</code></li> </ul> <p dir="auto"><strong>Fix</strong>: Use the <code>webServer</code> config option to let Playwright start and manage the dev server automatically.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — playwright.config.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">webServer</span><span class="o">:</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run dev"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="nx">timeout</span>: <span class="kt">120_000</span><span class="p">,</span> <span class="c1">// 2 minutes for server start </span> <span class="p">},</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — playwright.config.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">webServer</span><span class="o">:</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run dev"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">120_000</span><span class="p">,</span> <span class="p">},</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="configuration.md">core/configuration.md</a>, <a href="../ci/ci-github-actions.md">ci/ci-github-actions.md</a></p> <hr> <h3>"page.goto: Timeout 30000ms exceeded"</h3> <p dir="auto"><strong>Cause</strong>: The page did not reach the <code>load</code> state (by default) within the navigation timeout. The server responded but the page took too long to fully load.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Large assets (images, fonts, scripts) slow the page load</li> <li>Third-party scripts (analytics, ads) blocking the load event</li> <li>The page makes many API calls before becoming interactive</li> <li>The server responded with a redirect chain that takes too long</li> </ul> <p dir="auto"><strong>Fix</strong>: Use a more appropriate <code>waitUntil</code> option or increase the navigation timeout.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// Use 'domcontentloaded' instead of 'load' if you don't need all assets </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s2">"domcontentloaded"</span> <span class="p">})</span> <span class="c1">// Or increase the navigation timeout for known slow pages </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span>: <span class="kt">60_000</span> <span class="p">})</span> <span class="c1">// Or set it globally in config </span><span class="c1">// playwright.config.ts </span><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">navigationTimeout</span>: <span class="kt">60_000</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s2">"domcontentloaded"</span> <span class="p">})</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60_000</span> <span class="p">})</span> <span class="c1">// playwright.config.js </span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">navigationTimeout</span><span class="o">:</span> <span class="mi">60_000</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="configuration.md">core/configuration.md</a></p> <hr> <h3>"Error: page.goto: Navigation failed because page was closed!"</h3> <p dir="auto"><strong>Cause</strong>: The page or browser context was closed while <code>goto()</code> was still in progress. This typically happens in teardown/cleanup race conditions.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li><code>afterEach</code> or a fixture closes the page/context before navigation completes</li> <li>Another test or hook called <code>browser.close()</code> prematurely</li> <li>A test failed and the cleanup ran while an async navigation was still pending</li> <li>Using <code>test.fixme()</code> or <code>test.skip()</code> after an async operation has started</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure cleanup only runs after all page operations complete. Use fixtures for lifecycle management instead of manual <code>afterEach</code>.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — race condition between test body and cleanup </span><span class="nx">test</span><span class="p">.</span><span class="nx">afterEach</span><span class="p">(</span><span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="c1">// may race with in-flight navigation </span><span class="p">})</span> <span class="c1">// GOOD — let Playwright manage page lifecycle via fixtures </span><span class="c1">// Playwright automatically creates and destroys the page per test </span><span class="c1">// No manual cleanup needed </span> <span class="c1">// If you need custom cleanup, make sure to await all navigation first </span><span class="nx">test</span><span class="p">(</span><span class="s2">"navigates to dashboard"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">responsePromise</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForResponse</span><span class="p">(</span><span class="s2">"**/api/user"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">responsePromise</span> <span class="c1">// ensure all async ops are done </span> <span class="c1">// test assertions here </span><span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — same pattern </span> <span class="nx">test</span><span class="p">(</span><span class="s2">"navigates to dashboard"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">responsePromise</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForResponse</span><span class="p">(</span><span class="s2">"**/api/user"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">responsePromise</span> <span class="c1">// test assertions here </span><span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="fixtures-and-hooks.md">core/fixtures-and-hooks.md</a></p> <hr> <h3>"page.waitForNavigation: Timeout 30000ms exceeded"</h3> <p dir="auto"><strong>Cause</strong>: <code>waitForNavigation()</code> waited for a full page navigation that never happened. In SPAs, client-side routing does not trigger a navigation event.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Using <code>waitForNavigation()</code> with a SPA that uses React Router, Vue Router, or Next.js client-side navigation</li> <li>The navigation already happened before <code>waitForNavigation()</code> was called (race condition)</li> <li>Using the deprecated pattern when <code>waitForURL()</code> is the correct replacement</li> </ul> <p dir="auto"><strong>Fix</strong>: Replace <code>waitForNavigation()</code> with <code>waitForURL()</code> which works with both SPAs and traditional page loads.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — deprecated, race-prone, doesn't work with SPA routing </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForNavigation</span><span class="p">()</span> <span class="c1">// GOOD — works with SPAs and full navigations </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Dashboard"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="s2">"**/dashboard"</span><span class="p">)</span> <span class="c1">// GOOD — combine with glob patterns </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="sr">/\/dashboard\/\d+/</span><span class="p">)</span> <span class="c1">// GOOD — for SPA route changes, assert on visible content </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Settings"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Settings"</span> <span class="p">})).</span><span class="nx">toBeVisible</span><span class="p">()</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// BAD — deprecated, race-prone </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForNavigation</span><span class="p">()</span> <span class="c1">// GOOD — works with SPAs and full navigations </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Dashboard"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="s2">"**/dashboard"</span><span class="p">)</span> <span class="c1">// GOOD — combine with glob patterns </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="sr">/\/dashboard\/\d+/</span><span class="p">)</span> <span class="c1">// GOOD — for SPA route changes, assert on visible content </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Settings"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Settings"</span> <span class="p">})).</span><span class="nx">toBeVisible</span><span class="p">()</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a></p> <hr> <h3>"Error: frame.goto: Frame was detached"</h3> <p dir="auto"><strong>Cause</strong>: The iframe was removed from the DOM (detached) while Playwright was navigating or interacting with it.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>The parent page re-rendered and recreated the iframe element</li> <li>JavaScript on the parent page removed and re-added the iframe</li> <li>A SPA route change unmounted the component containing the iframe</li> <li>An ad iframe reloaded itself</li> </ul> <p dir="auto"><strong>Fix</strong>: Re-acquire the frame reference after the parent page updates.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — frame reference becomes stale </span><span class="kr">const</span> <span class="nx">frame</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">)</span> <span class="c1">// ... parent page re-renders ... </span><span class="k">await</span> <span class="nx">frame</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Frame was detached </span> <span class="c1">// GOOD — use frameLocator (re-evaluates each time) </span><span class="c1">// frameLocator itself is lazy, but the frame it points to must exist </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// GOOD — wait for the iframe to be present after a re-render </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">).</span><span class="nx">click</span><span class="p">()</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — frameLocator re-evaluates lazily </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// GOOD — wait for the iframe to be present after a re-render </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">frameLocator</span><span class="p">(</span><span class="s2">"#my-iframe"</span><span class="p">).</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">).</span><span class="nx">click</span><span class="p">()</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="iframes-and-shadow-dom.md">core/iframes-and-shadow-dom.md</a></p> <hr> <h2>Test Framework Errors</h2> <hr> <h3>"Error: Test timeout of 30000ms exceeded"</h3> <p dir="auto"><strong>Cause</strong>: The entire test (including all hooks and assertions) did not complete within the configured test timeout.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Waiting for an element that never appears (wrong locator, missing data)</li> <li>Unresolved <code>page.waitForEvent()</code> — the expected event never fires</li> <li>Slow test environment (CI with limited resources)</li> <li>Too many sequential actions in a single test</li> <li>A <code>beforeEach</code> hook performs expensive setup (login, data seeding)</li> </ul> <p dir="auto"><strong>Fix</strong>: Identify the slow step using <code>test.step()</code> and traces, then either fix the root cause or adjust the timeout.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// Increase timeout for a specific slow test </span><span class="nx">test</span><span class="p">(</span><span class="s2">"generates large report"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">test</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">120</span><span class="nx">_000</span><span class="p">)</span> <span class="c1">// 2 minutes for this test only </span> <span class="c1">// ... </span><span class="p">})</span> <span class="c1">// Increase timeout globally in config </span><span class="c1">// playwright.config.ts </span><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">timeout</span>: <span class="kt">60_000</span><span class="p">,</span> <span class="c1">// 60 seconds per test </span><span class="p">})</span> <span class="c1">// Better: diagnose with test.step() to find what's slow </span><span class="nx">test</span><span class="p">(</span><span class="s2">"checkout flow"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">test</span><span class="p">.</span><span class="nx">step</span><span class="p">(</span><span class="s2">"add item to cart"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/products"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Add to Cart"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="p">})</span> <span class="k">await</span> <span class="nx">test</span><span class="p">.</span><span class="nx">step</span><span class="p">(</span><span class="s2">"complete checkout"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Cart"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Checkout"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="nx">test</span><span class="p">(</span><span class="s2">"generates large report"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="nx">test</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(</span><span class="mi">120_000</span><span class="p">)</span> <span class="c1">// ... </span><span class="p">})</span> <span class="c1">// playwright.config.js </span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60_000</span><span class="p">,</span> <span class="p">})</span> <span class="nx">test</span><span class="p">(</span><span class="s2">"checkout flow"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">test</span><span class="p">.</span><span class="nx">step</span><span class="p">(</span><span class="s2">"add item to cart"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/products"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Add to Cart"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="p">})</span> <span class="kr">await</span> <span class="nx">test</span><span class="p">.</span><span class="nx">step</span><span class="p">(</span><span class="s2">"complete checkout"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"link"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Cart"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Checkout"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="configuration.md">core/configuration.md</a>, <a href="debugging.md">core/debugging.md</a></p> <hr> <h3>"Error: expect(received).toMatchSnapshot()"</h3> <p dir="auto"><strong>Cause</strong>: The current screenshot or text does not match the stored baseline snapshot.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Legitimate UI changes that require updating the snapshot</li> <li>Different rendering between local and CI (fonts, anti-aliasing, OS-level rendering)</li> <li>Dynamic content (timestamps, random IDs, ads) that changes between runs</li> <li>Different viewport sizes or device emulation settings between environments</li> </ul> <p dir="auto"><strong>Fix</strong>: Update the snapshot if the change is intentional, or mask/hide dynamic areas.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// Update snapshots after intentional changes </span><span class="c1">// Run: npx playwright test --update-snapshots </span> <span class="c1">// Mask dynamic content before taking a screenshot </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveScreenshot</span><span class="p">(</span><span class="s2">"dashboard.png"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">mask</span><span class="o">:</span> <span class="p">[</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".timestamp"</span><span class="p">),</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".random-ad"</span><span class="p">)],</span> <span class="nx">maxDiffPixelRatio</span>: <span class="kt">0.01</span><span class="p">,</span> <span class="c1">// allow 1% pixel difference </span><span class="p">})</span> <span class="c1">// Use text snapshot with a sanitizer for dynamic values </span><span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">).</span><span class="nx">textContent</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">sanitized</span> <span class="o">=</span> <span class="nx">content</span><span class="o">!</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\d{4}-\d{2}-\d{2}/g</span><span class="p">,</span> <span class="s2">"DATE"</span><span class="p">)</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">sanitized</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">(</span><span class="s2">"dashboard-content.txt"</span><span class="p">)</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// Update snapshots: npx playwright test --update-snapshots </span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveScreenshot</span><span class="p">(</span><span class="s2">"dashboard.png"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">mask</span><span class="o">:</span> <span class="p">[</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".timestamp"</span><span class="p">),</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".random-ad"</span><span class="p">)],</span> <span class="nx">maxDiffPixelRatio</span><span class="o">:</span> <span class="mf">0.01</span><span class="p">,</span> <span class="p">})</span> <span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">).</span><span class="nx">textContent</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">sanitized</span> <span class="o">=</span> <span class="nx">content</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\d{4}-\d{2}-\d{2}/g</span><span class="p">,</span> <span class="s2">"DATE"</span><span class="p">)</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">sanitized</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">(</span><span class="s2">"dashboard-content.txt"</span><span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="visual-regression.md">core/visual-regression.md</a></p> <hr> <h3>"Error: browserType.launch: Executable doesn't exist"</h3> <p dir="auto"><strong>Cause</strong>: The Playwright browser binaries are not installed on this machine or they were installed for a different Playwright version.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Fresh clone of a project without running the install step</li> <li>Upgrading <code>@playwright/test</code> without reinstalling browsers</li> <li>CI environment missing the install step</li> <li>Using <code>npm ci</code> which cleans <code>node_modules</code> but does not trigger the browser install postinstall script</li> <li>Multiple Playwright versions installed (global vs local)</li> </ul> <p dir="auto"><strong>Fix</strong>: Install the browsers.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display"><span class="c1"># Install all browsers</span> npx playwright install <span class="c1"># Install only the browser you need (faster in CI)</span> npx playwright install chromium <span class="c1"># Install browsers with OS dependencies (needed in CI/Docker)</span> npx playwright install --with-deps <span class="c1"># If you're on a CI Dockerfile, add this to your Dockerfile</span> <span class="c1"># RUN npx playwright install --with-deps chromium</span> </code></pre></div><p dir="auto">For CI pipelines, always include the install step:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-yaml display"><span class="c"># GitHub Actions example</span><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Playwright Browsers</span><span class="w"> </span><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">npx playwright install --with-deps</span><span class="w"> </span></code></pre></div><p dir="auto"><strong>Related</strong>: <a href="../ci/ci-github-actions.md">ci/ci-github-actions.md</a>, <a href="../ci/docker-and-containers.md">ci/docker-and-containers.md</a></p> <hr> <h3>"Error: Cannot use import statement outside a module"</h3> <p dir="auto"><strong>Cause</strong>: Node.js is running the test file as CommonJS but the file uses ES module <code>import</code> syntax without the right configuration.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Missing or misconfigured <code>tsconfig.json</code></li> <li>Using <code>.ts</code> files without <code>@playwright/test</code> properly resolving TypeScript</li> <li><code>package.json</code> does not have <code>"type": "module"</code> but test files use <code>import</code></li> <li>Running test files directly with <code>node</code> instead of through <code>npx playwright test</code></li> <li>Conflicting Babel or ts-node configuration</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure TypeScript is configured correctly and always run tests through the Playwright CLI.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display"><span class="c1"># Always run tests through the Playwright CLI — never with node directly</span> npx playwright <span class="nb">test</span> <span class="c1"># NOT this:</span> <span class="c1"># node tests/example.spec.ts <-- will fail</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — tsconfig.json (minimal working config) </span><span class="p">{</span> <span class="s2">"compilerOptions"</span><span class="o">:</span> <span class="p">{</span> <span class="s2">"target"</span><span class="o">:</span> <span class="s2">"ES2020"</span><span class="p">,</span> <span class="s2">"module"</span><span class="o">:</span> <span class="s2">"ESNext"</span><span class="p">,</span> <span class="s2">"moduleResolution"</span><span class="o">:</span> <span class="s2">"bundler"</span><span class="p">,</span> <span class="s2">"strict"</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="s2">"esModuleInterop"</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="s2">"skipLibCheck"</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div><p dir="auto">If using JavaScript without TypeScript:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — ensure package.json has "type": "module" </span><span class="c1">// Or rename files to .mjs </span> <span class="c1">// In playwright.config.js — use require if package.json lacks "type": "module" </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="cm">/* ... */</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="configuration.md">core/configuration.md</a></p> <hr> <h3>"Error: fixture "xxx" has already been registered"</h3> <p dir="auto"><strong>Cause</strong>: A custom fixture was defined with the same name in multiple <code>test.extend()</code> calls that got merged, or the same fixture name was registered twice in the same extension.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Two different fixture files both define a fixture with the same name</li> <li>Importing the extended <code>test</code> object from multiple files that each add overlapping fixtures</li> <li>Copy-pasting fixture definitions without renaming</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure each fixture name is unique across your merged fixture chain. Use a single fixture file that combines all extensions.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — two separate extensions with the same fixture name </span><span class="c1">// fixtures/auth.ts </span><span class="kr">export</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="o"><</span><span class="p">{</span> <span class="nx">user</span>: <span class="kt">User</span> <span class="p">}</span><span class="o">></span><span class="p">({</span> <span class="nx">user</span>: <span class="kt">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span> <span class="p">})</span> <span class="c1">// fixtures/data.ts — CONFLICT: "user" already exists </span><span class="kr">export</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="o"><</span><span class="p">{</span> <span class="nx">user</span>: <span class="kt">User</span> <span class="p">}</span><span class="o">></span><span class="p">({</span> <span class="nx">user</span>: <span class="kt">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span> <span class="p">})</span> <span class="c1">// GOOD — single combined fixture file </span><span class="c1">// fixtures/index.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="kr">as</span> <span class="nx">base</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">type</span> <span class="nx">MyFixtures</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">authenticatedUser</span>: <span class="kt">User</span> <span class="nx">testData</span>: <span class="kt">TestData</span> <span class="p">}</span> <span class="kr">export</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="p"><</span><span class="nt">MyFixtures</span><span class="p">>({</span> <span class="nx">authenticatedUser</span>: <span class="kt">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">createUser</span><span class="p">()</span> <span class="k">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="k">await</span> <span class="nx">deleteUser</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">},</span> <span class="nx">testData</span>: <span class="kt">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">seedData</span><span class="p">()</span> <span class="k">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="k">await</span> <span class="nx">cleanupData</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">},</span> <span class="p">})</span> <span class="kr">export</span> <span class="p">{</span> <span class="nx">expect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — single combined fixture file </span><span class="c1">// fixtures/index.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">test</span><span class="o">:</span> <span class="nx">base</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span> <span class="nx">authenticatedUser</span><span class="o">:</span> <span class="kr">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">createUser</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">deleteUser</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">},</span> <span class="nx">testData</span><span class="o">:</span> <span class="kr">async</span> <span class="p">({},</span> <span class="nx">use</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">seedData</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">cleanupData</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">},</span> <span class="p">})</span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">test</span> <span class="p">}</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="fixtures-and-hooks.md">core/fixtures-and-hooks.md</a></p> <hr> <h2>Network & API Errors</h2> <hr> <h3>"page.route: Pattern should start with..."</h3> <p dir="auto"><strong>Cause</strong>: The URL pattern passed to <code>page.route()</code> does not match the expected format. Playwright expects a glob pattern (starting with <code>**/</code> or <code>http</code>), a regex, or a predicate function.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Passing a bare path like <code>/api/users</code> instead of <code>**/api/users</code></li> <li>Missing the <code>**</code> glob prefix for relative patterns</li> <li>Passing an object or invalid type instead of a string/regex</li> </ul> <p dir="auto"><strong>Fix</strong>: Use the correct pattern format.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — bare path without glob prefix </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"/api/users"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">body</span><span class="o">:</span> <span class="s2">"[]"</span> <span class="p">}))</span> <span class="c1">// GOOD — glob pattern </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/users"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span>: <span class="kt">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span>: <span class="kt">JSON.stringify</span><span class="p">([{</span> <span class="nx">id</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Alice"</span> <span class="p">}]),</span> <span class="p">})</span> <span class="p">)</span> <span class="c1">// GOOD — regex pattern </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="sr">/\/api\/users\/\d+/</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span>: <span class="kt">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span>: <span class="kt">JSON.stringify</span><span class="p">({</span> <span class="nx">id</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Alice"</span> <span class="p">}),</span> <span class="p">})</span> <span class="p">)</span> <span class="c1">// GOOD — predicate function for complex matching </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">=></span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">"/api/"</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="s2">"filter"</span><span class="p">),</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">body</span><span class="o">:</span> <span class="s2">"[]"</span> <span class="p">})</span> <span class="p">)</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — glob pattern </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/users"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([{</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Alice"</span> <span class="p">}]),</span> <span class="p">})</span> <span class="p">)</span> <span class="c1">// GOOD — regex pattern </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="sr">/\/api\/users\/\d+/</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">id</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Alice"</span> <span class="p">}),</span> <span class="p">})</span> <span class="p">)</span> <span class="c1">// GOOD — predicate function </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">=></span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">"/api/"</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="s2">"filter"</span><span class="p">),</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">body</span><span class="o">:</span> <span class="s2">"[]"</span> <span class="p">})</span> <span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="network-mocking.md">core/network-mocking.md</a></p> <hr> <h3>"Error: apiRequestContext.get: connect ECONNREFUSED"</h3> <p dir="auto"><strong>Cause</strong>: The API request made via <code>request.get()</code> (or <code>.post()</code>, <code>.put()</code>, etc.) could not connect to the target server. Same underlying issue as <code>page.goto: net::ERR_CONNECTION_REFUSED</code> but for API testing.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>API server not running when using <code>request</code> fixture</li> <li>Wrong <code>baseURL</code> in the config or in the API request context</li> <li>Running API tests against a service that hasn't started yet</li> <li>In CI, the API service container is not ready</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure the API server is running. Use <code>webServer</code> config to auto-start it, or add a health check.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — playwright.config.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">webServer</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run start:api"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:4000/health" data-markdown-generated-content="">http://localhost:4000/health</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run start:web"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="p">},</span> <span class="p">],</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> <span class="c1">// In tests, use the request fixture with explicit baseURL for API calls </span><span class="nx">test</span><span class="p">(</span><span class="s2">"fetch users"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">request</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s2">"<a href="http://localhost:4000/api/users" data-markdown-generated-content="">http://localhost:4000/api/users</a>"</span><span class="p">)</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">()).</span><span class="nx">toBeTruthy</span><span class="p">()</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — playwright.config.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">webServer</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run start:api"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:4000/health" data-markdown-generated-content="">http://localhost:4000/health</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">command</span><span class="o">:</span> <span class="s2">"npm run start:web"</span><span class="p">,</span> <span class="nx">url</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="nx">reuseExistingServer</span><span class="o">:</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span><span class="p">,</span> <span class="p">},</span> <span class="p">],</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">"<a href="http://localhost:3000" data-markdown-generated-content="">http://localhost:3000</a>"</span><span class="p">,</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="api-testing.md">core/api-testing.md</a>, <a href="configuration.md">core/configuration.md</a></p> <hr> <h3>"Request was not handled: GET https://..."</h3> <p dir="auto"><strong>Cause</strong>: A route handler was registered with <code>page.route()</code> using <code>{ times: N }</code> or <code>page.unrouteAll()</code> was called, and a subsequent request matching that pattern was not intercepted — Playwright warns you that a request fell through.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Using <code>page.route()</code> with <code>{ times: 1 }</code> but the app makes the same request more than once</li> <li>Calling <code>route.fallback()</code> or <code>route.continue()</code> without a handler to catch it</li> <li>Calling <code>page.unrouteAll({ behavior: 'wait' })</code> mid-test and the app makes another request</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure your route handler covers all expected requests, or let unhandled requests pass through.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — only handles the first request, second one is unhandled </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/data"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">body</span><span class="o">:</span> <span class="s2">"{}"</span> <span class="p">}),</span> <span class="p">{</span> <span class="nx">times</span>: <span class="kt">1</span> <span class="p">})</span> <span class="c1">// GOOD — handle all requests to this URL </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/data"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span>: <span class="kt">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span>: <span class="kt">JSON.stringify</span><span class="p">({</span> <span class="nx">items</span><span class="o">:</span> <span class="p">[]</span> <span class="p">}),</span> <span class="p">})</span> <span class="p">)</span> <span class="c1">// GOOD — if you only want to intercept once, use waitForResponse for subsequent </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/data"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="o">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">body</span><span class="o">:</span> <span class="s1">'{"items": []}'</span> <span class="p">}),</span> <span class="p">{</span> <span class="nx">times</span>: <span class="kt">1</span> <span class="p">})</span> <span class="c1">// For the second call, let it go to the real server (no route needed) </span></code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — handle all requests to this URL </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s2">"**/api/data"</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=></span> <span class="nx">route</span><span class="p">.</span><span class="nx">fulfill</span><span class="p">({</span> <span class="nx">status</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="nx">contentType</span><span class="o">:</span> <span class="s2">"application/json"</span><span class="p">,</span> <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">items</span><span class="o">:</span> <span class="p">[]</span> <span class="p">}),</span> <span class="p">})</span> <span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="network-mocking.md">core/network-mocking.md</a></p> <hr> <h2>Authentication & State Errors</h2> <hr> <h3>"Error: browserContext.storageState: No such file"</h3> <p dir="auto"><strong>Cause</strong>: The <code>storageState</code> path referenced in the config or test does not point to an existing file. The auth state file has not been generated yet.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Running tests before running the auth setup project</li> <li>The <code>globalSetup</code> or auth setup project did not complete successfully</li> <li>The file path in the config does not match the path used in the setup</li> <li><code>.gitignore</code> excluded the storage state file and it was not regenerated after a fresh clone</li> <li>CI cache expired and the state file was not recreated</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure the auth setup runs first and the file path is consistent.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — playwright.config.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">projects</span><span class="o">:</span> <span class="p">[</span> <span class="c1">// Setup project runs first and creates the auth state file </span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"setup"</span><span class="p">,</span> <span class="nx">testMatch</span><span class="o">:</span> <span class="sr">/.*\.setup\.ts/</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"chromium"</span><span class="p">,</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">storageState</span>: <span class="kt">authFile</span><span class="p">,</span> <span class="p">},</span> <span class="nx">dependencies</span><span class="o">:</span> <span class="p">[</span><span class="s2">"setup"</span><span class="p">],</span> <span class="c1">// ensures setup runs first </span> <span class="p">},</span> <span class="p">],</span> <span class="p">})</span> <span class="c1">// auth.setup.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="kr">as</span> <span class="nx">setup</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="nx">setup</span><span class="p">(</span><span class="s2">"authenticate"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Password"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"password123"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Sign in"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="c1">// Save the authenticated state </span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">context</span><span class="p">().</span><span class="nx">storageState</span><span class="p">({</span> <span class="nx">path</span>: <span class="kt">authFile</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — playwright.config.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">projects</span><span class="o">:</span> <span class="p">[</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"setup"</span><span class="p">,</span> <span class="nx">testMatch</span><span class="o">:</span> <span class="sr">/.*\.setup\.js/</span><span class="p">,</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"chromium"</span><span class="p">,</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">storageState</span><span class="o">:</span> <span class="nx">authFile</span><span class="p">,</span> <span class="p">},</span> <span class="nx">dependencies</span><span class="o">:</span> <span class="p">[</span><span class="s2">"setup"</span><span class="p">],</span> <span class="p">},</span> <span class="p">],</span> <span class="p">})</span> <span class="c1">// auth.setup.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">test</span><span class="o">:</span> <span class="nx">setup</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="nx">setup</span><span class="p">(</span><span class="s2">"authenticate"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Password"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"password123"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Sign in"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">context</span><span class="p">().</span><span class="nx">storageState</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="nx">authFile</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><p dir="auto">Ensure the auth directory exists and is in <code>.gitignore</code>:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display">mkdir -p playwright/.auth <span class="nb">echo</span> <span class="s2">"playwright/.auth/"</span> >> .gitignore </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="authentication.md">core/authentication.md</a>, <a href="auth-flows.md">core/auth-flows.md</a></p> <hr> <h3>"Error: Target page, context or browser has been closed"</h3> <p dir="auto"><strong>Cause</strong>: Code attempted to use a <code>page</code>, <code>context</code>, or <code>browser</code> object that has already been closed or disposed.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Using a <code>page</code> reference after <code>page.close()</code> was called</li> <li>Storing a <code>page</code> or <code>context</code> in a module-level variable and reusing it across tests</li> <li>A fixture tore down the browser context while an async callback was still running</li> <li>A <code>beforeAll</code> created a context that was closed before all tests finished using it</li> <li>Using <code>browser.newContext()</code> manually and forgetting to manage its lifecycle</li> </ul> <p dir="auto"><strong>Fix</strong>: Never store page/context references in shared mutable state. Use Playwright fixtures for lifecycle management.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — shared module-level variable </span><span class="kd">let</span> <span class="nx">sharedPage</span>: <span class="kt">Page</span> <span class="nx">test</span><span class="p">.</span><span class="nx">beforeAll</span><span class="p">(</span><span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">sharedPage</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span> <span class="p">})</span> <span class="nx">test</span><span class="p">.</span><span class="nx">afterAll</span><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">sharedPage</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="p">})</span> <span class="nx">test</span><span class="p">(</span><span class="s2">"first"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">sharedPage</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span> <span class="c1">// might fail if afterAll from another describe ran first </span><span class="p">})</span> <span class="c1">// GOOD — use the built-in page fixture (one per test, auto-managed) </span><span class="nx">test</span><span class="p">(</span><span class="s2">"first"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span> <span class="c1">// always a fresh, open page </span><span class="p">})</span> <span class="c1">// GOOD — for shared state across tests, use a worker-scoped fixture </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="kr">as</span> <span class="nx">base</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">export</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="o"><</span><span class="p">{},</span> <span class="p">{</span> <span class="nx">adminContext</span>: <span class="kt">BrowserContext</span> <span class="p">}</span><span class="o">></span><span class="p">({</span> <span class="nx">adminContext</span><span class="o">:</span> <span class="p">[</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="k">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="k">await</span> <span class="nx">context</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">scope</span><span class="o">:</span> <span class="s2">"worker"</span> <span class="p">},</span> <span class="p">],</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — use the built-in page fixture </span><span class="nx">test</span><span class="p">(</span><span class="s2">"first"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span> <span class="p">})</span> <span class="c1">// GOOD — for shared state, use a worker-scoped fixture </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">test</span><span class="o">:</span> <span class="nx">base</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">base</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span> <span class="nx">adminContext</span><span class="o">:</span> <span class="p">[</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">},</span> <span class="nx">use</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">context</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="p">},</span> <span class="p">{</span> <span class="nx">scope</span><span class="o">:</span> <span class="s2">"worker"</span> <span class="p">},</span> <span class="p">],</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="fixtures-and-hooks.md">core/fixtures-and-hooks.md</a></p> <hr> <h2>CI-Specific Errors</h2> <hr> <h3>"Error: browserType.launch: Browser closed unexpectedly"</h3> <p dir="auto"><strong>Cause</strong>: The browser process crashed immediately after launch, usually because required OS-level dependencies are missing in the CI environment.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Running on a minimal Docker image (e.g., <code>node:alpine</code>) without browser dependencies</li> <li>Missing shared libraries (<code>libgbm</code>, <code>libnss3</code>, <code>libatk-bridge</code>, etc.) on Linux CI</li> <li>Insufficient shared memory (<code>/dev/shm</code> too small) — browsers use shared memory for rendering</li> <li>Running as root in Docker without <code>--no-sandbox</code> (Chromium refuses to run as root with sandbox)</li> <li>Out-of-memory kill on CI (browser process is OOM-killed by the OS)</li> </ul> <p dir="auto"><strong>Fix</strong>: Install OS-level dependencies and configure the environment.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display"><span class="c1"># Install browsers with their OS dependencies (recommended)</span> npx playwright install --with-deps chromium <span class="c1"># If using Docker, use the official Playwright image</span> <span class="c1"># Dockerfile</span> FROM mcr.microsoft.com/playwright:v1.50.0-noble WORKDIR /app COPY . . RUN npm ci RUN npx playwright install chromium CMD <span class="o">[</span><span class="s2">"npx"</span>, <span class="s2">"playwright"</span>, <span class="s2">"test"</span><span class="o">]</span> </code></pre></div><p dir="auto">For shared memory issues:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-yaml display"><span class="c"># GitHub Actions — increase /dev/shm</span><span class="w"> </span><span class="nt">jobs</span><span class="p">:</span><span class="w"> </span><span class="w"> </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w"> </span><span class="w"> </span><span class="nt">container</span><span class="p">:</span><span class="w"> </span><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mcr.microsoft.com/playwright:v1.50.0-noble</span><span class="w"> </span><span class="w"> </span><span class="nt">options</span><span class="p">:</span><span class="w"> </span>--<span class="l">shm-size=2gb</span><span class="w"> </span><span class="w"> </span><span class="c"># Docker run — increase shared memory</span><span class="w"> </span><span class="c"># docker run --shm-size=2gb my-test-image</span><span class="w"> </span></code></pre></div><p dir="auto">For running as root in Docker:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — playwright.config.ts (only for Docker/CI) </span><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">launchOptions</span><span class="o">:</span> <span class="p">{</span> <span class="nx">args</span>: <span class="kt">process.env.CI</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"--no-sandbox"</span><span class="p">,</span> <span class="s2">"--disable-setuid-sandbox"</span><span class="p">]</span> <span class="o">:</span> <span class="p">[],</span> <span class="p">},</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — playwright.config.js </span><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">use</span><span class="o">:</span> <span class="p">{</span> <span class="nx">launchOptions</span><span class="o">:</span> <span class="p">{</span> <span class="nx">args</span><span class="o">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"--no-sandbox"</span><span class="p">,</span> <span class="s2">"--disable-setuid-sandbox"</span><span class="p">]</span> <span class="o">:</span> <span class="p">[],</span> <span class="p">},</span> <span class="p">},</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="../ci/ci-github-actions.md">ci/ci-github-actions.md</a>, <a href="../ci/docker-and-containers.md">ci/docker-and-containers.md</a></p> <hr> <h3>"Error: Playwright Test needs to be invoked via 'npx playwright test'"</h3> <p dir="auto"><strong>Cause</strong>: The test file was executed directly with <code>node</code> or <code>ts-node</code> instead of through the Playwright test runner.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Running <code>node tests/example.spec.ts</code> or <code>ts-node tests/example.spec.ts</code></li> <li>A misconfigured IDE run configuration that invokes <code>node</code> directly</li> <li>Using Jest or Mocha to run Playwright test files that import from <code>@playwright/test</code></li> <li>A CI script that calls the wrong command</li> </ul> <p dir="auto"><strong>Fix</strong>: Always use the Playwright CLI to run tests.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display"><span class="c1"># Correct invocations</span> npx playwright <span class="nb">test</span> <span class="c1"># run all tests</span> npx playwright <span class="nb">test</span> tests/login.spec.ts <span class="c1"># run specific file</span> npx playwright <span class="nb">test</span> --grep <span class="s2">"login"</span> <span class="c1"># run tests matching pattern</span> npx playwright <span class="nb">test</span> --project<span class="o">=</span>chromium <span class="c1"># run specific project</span> npx playwright <span class="nb">test</span> --debug <span class="c1"># run with inspector</span> <span class="c1"># If using a package.json script</span> <span class="c1"># package.json</span> <span class="c1"># "scripts": { "test:e2e": "playwright test" }</span> npm run test:e2e </code></pre></div><p dir="auto">For IDE configuration (VS Code):</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-json display"><span class="c1">// .vscode/settings.json — install the Playwright VS Code extension instead of custom configs </span><span class="p">{</span> <span class="nt">"playwright.reuseBrowser"</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="configuration.md">core/configuration.md</a></p> <hr> <h2>Additional Common Errors</h2> <hr> <h3>"Error: page.evaluate: Execution context was destroyed"</h3> <p dir="auto"><strong>Cause</strong>: The page navigated or reloaded while <code>page.evaluate()</code> was executing JavaScript in the browser context.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Calling <code>evaluate()</code> while the page is in the middle of a navigation</li> <li>A timer or event on the page triggers a redirect during evaluation</li> <li>Running <code>evaluate()</code> in a <code>beforeEach</code> that races with <code>page.goto()</code></li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure the page is stable before evaluating.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — evaluate races with navigation </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="o">=></span> <span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">)</span> <span class="c1">// context might be destroyed </span> <span class="c1">// GOOD — wait for load state first </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForLoadState</span><span class="p">(</span><span class="s2">"domcontentloaded"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="o">=></span> <span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">)</span> <span class="c1">// GOOD — prefer locator-based assertions (no evaluate needed) </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="s2">"Dashboard"</span><span class="p">)</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — wait for load state first </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForLoadState</span><span class="p">(</span><span class="s2">"domcontentloaded"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="p">=></span> <span class="nb">document</span><span class="p">.</span><span class="nx">title</span><span class="p">)</span> <span class="c1">// GOOD — prefer locator-based assertions </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="s2">"Dashboard"</span><span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a></p> <hr> <h3>"Error: page.screenshot: Cannot take a screenshot larger than..."</h3> <p dir="auto"><strong>Cause</strong>: The requested screenshot dimensions exceed Playwright's limit (typically 16384x16384 pixels). This happens with full-page screenshots of very long pages.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li><code>page.screenshot({ fullPage: true })</code> on an infinitely-scrolling page</li> <li>Pages with extremely tall content (long data tables, chat logs)</li> <li>CSS bugs that create enormous element heights</li> </ul> <p dir="auto"><strong>Fix</strong>: Clip the screenshot to a specific region or limit the viewport.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — might exceed size limits on tall pages </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">fullPage</span>: <span class="kt">true</span><span class="p">,</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"full.png"</span> <span class="p">})</span> <span class="c1">// GOOD — clip to a specific region </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"clipped.png"</span><span class="p">,</span> <span class="nx">clip</span><span class="o">:</span> <span class="p">{</span> <span class="nx">x</span>: <span class="kt">0</span><span class="p">,</span> <span class="nx">y</span>: <span class="kt">0</span><span class="p">,</span> <span class="nx">width</span>: <span class="kt">1280</span><span class="p">,</span> <span class="nx">height</span>: <span class="kt">2000</span> <span class="p">},</span> <span class="p">})</span> <span class="c1">// GOOD — screenshot a specific element instead </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">).</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"main-content.png"</span> <span class="p">})</span> <span class="c1">// GOOD — set a reasonable viewport before full-page screenshot </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">setViewportSize</span><span class="p">({</span> <span class="nx">width</span>: <span class="kt">1280</span><span class="p">,</span> <span class="nx">height</span>: <span class="kt">720</span> <span class="p">})</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">fullPage</span>: <span class="kt">true</span><span class="p">,</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"full.png"</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — clip to a specific region </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"clipped.png"</span><span class="p">,</span> <span class="nx">clip</span><span class="o">:</span> <span class="p">{</span> <span class="nx">x</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">y</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">width</span><span class="o">:</span> <span class="mi">1280</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">2000</span> <span class="p">},</span> <span class="p">})</span> <span class="c1">// GOOD — screenshot a specific element </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">).</span><span class="nx">screenshot</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">"main-content.png"</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="visual-regression.md">core/visual-regression.md</a></p> <hr> <h3>"Error: waiting for locator('...').toBeAttached()"</h3> <p dir="auto"><strong>Cause</strong>: The element never entered the DOM within the assertion timeout. Unlike <code>toBeVisible()</code>, <code>toBeAttached()</code> only checks DOM presence — but the element was never rendered at all.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>The component is conditionally rendered and the condition is not met</li> <li>A network request that populates the data has not completed</li> <li>Wrong selector that does not match any element</li> <li>The element is rendered by a lazy-loaded chunk that has not downloaded yet</li> </ul> <p dir="auto"><strong>Fix</strong>: Verify the precondition for the element to render.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// Ensure the data loads before asserting </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForResponse</span><span class="p">((</span><span class="nx">resp</span><span class="p">)</span> <span class="o">=></span> <span class="nx">resp</span><span class="p">.</span><span class="nx">url</span><span class="p">().</span><span class="nx">includes</span><span class="p">(</span><span class="s2">"/api/users"</span><span class="p">))</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"table"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">()</span> <span class="c1">// For lazy-loaded content, wait longer or trigger the load </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"tab"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Advanced"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByTestId</span><span class="p">(</span><span class="s2">"advanced-panel"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">({</span> <span class="nx">timeout</span>: <span class="kt">10_000</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForResponse</span><span class="p">((</span><span class="nx">resp</span><span class="p">)</span> <span class="p">=></span> <span class="nx">resp</span><span class="p">.</span><span class="nx">url</span><span class="p">().</span><span class="nx">includes</span><span class="p">(</span><span class="s2">"/api/users"</span><span class="p">))</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"table"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"tab"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Advanced"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByTestId</span><span class="p">(</span><span class="s2">"advanced-panel"</span><span class="p">)).</span><span class="nx">toBeAttached</span><span class="p">({</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">10_000</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a>, <a href="locators.md">core/locators.md</a></p> <hr> <h3>"Error: protocol error: Target.createTarget: Failed to create target"</h3> <p dir="auto"><strong>Cause</strong>: The browser could not create a new tab or context, usually due to resource exhaustion.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Opening too many pages or contexts without closing them</li> <li>Memory leak in tests — each test opens a context but never closes it</li> <li>Running many parallel workers on a resource-constrained CI machine</li> <li>Browser process is unstable after a previous crash</li> </ul> <p dir="auto"><strong>Fix</strong>: Reduce parallelism, close unused contexts, or use fewer workers.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — playwright.config.ts </span> <span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="c1">// Reduce parallelism if resources are limited </span> <span class="nx">workers</span>: <span class="kt">process.env.CI</span> <span class="o">?</span> <span class="nx">2</span> : <span class="kt">undefined</span><span class="p">,</span> <span class="c1">// Limit to one browser context per worker </span> <span class="nx">fullyParallel</span>: <span class="kt">false</span><span class="p">,</span> <span class="c1">// run test files serially within a worker </span><span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — playwright.config.js </span> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">defineConfig</span><span class="p">({</span> <span class="nx">workers</span><span class="o">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CI</span> <span class="o">?</span> <span class="mi">2</span> <span class="o">:</span> <span class="kc">undefined</span><span class="p">,</span> <span class="nx">fullyParallel</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="p">})</span> </code></pre></div><p dir="auto">If you manually create contexts, always close them:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span><span class="nx">test</span><span class="p">(</span><span class="s2">"multi-user test"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">userContext</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">adminContext</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="k">try</span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">userPage</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">userContext</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">adminPage</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">adminContext</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span> <span class="c1">// ... test logic </span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="k">await</span> <span class="nx">userContext</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="k">await</span> <span class="nx">adminContext</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="p">}</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span><span class="nx">test</span><span class="p">(</span><span class="s2">"multi-user test"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">userContext</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">adminContext</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">()</span> <span class="k">try</span> <span class="p">{</span> <span class="kr">const</span> <span class="nx">userPage</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">userContext</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span> <span class="kr">const</span> <span class="nx">adminPage</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">adminContext</span><span class="p">.</span><span class="nx">newPage</span><span class="p">()</span> <span class="c1">// ... test logic </span> <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">userContext</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">adminContext</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span> <span class="p">}</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="../ci/parallel-and-sharding.md">ci/parallel-and-sharding.md</a>, <a href="multi-user-and-collaboration.md">core/multi-user-and-collaboration.md</a></p> <hr> <h3>"Error: expect(locator).toHaveText() — expected string but received array"</h3> <p dir="auto"><strong>Cause</strong>: The locator matched multiple elements and <code>toHaveText()</code> returned an array of strings instead of a single string.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Using a broad locator like <code>page.locator('.item')</code> that matches a list</li> <li>Expecting a single heading but the selector matches multiple</li> <li>Not scoping the locator to a specific container</li> </ul> <p dir="auto"><strong>Fix</strong>: Either narrow the locator to one element or use the array form of <code>toHaveText()</code>.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — locator matches multiple elements </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".item"</span><span class="p">)).</span><span class="nx">toHaveText</span><span class="p">(</span><span class="s2">"First Item"</span><span class="p">)</span> <span class="c1">// GOOD — narrow to one element </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".item"</span><span class="p">).</span><span class="nx">first</span><span class="p">()).</span><span class="nx">toHaveText</span><span class="p">(</span><span class="s2">"First Item"</span><span class="p">)</span> <span class="c1">// GOOD — assert against an array when multiple elements are expected </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".item"</span><span class="p">)).</span><span class="nx">toHaveText</span><span class="p">([</span><span class="s2">"First Item"</span><span class="p">,</span> <span class="s2">"Second Item"</span><span class="p">,</span> <span class="s2">"Third Item"</span><span class="p">])</span> <span class="c1">// GOOD — use a more specific locator </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">level</span>: <span class="kt">1</span> <span class="p">})).</span><span class="nx">toHaveText</span><span class="p">(</span><span class="s2">"Welcome"</span><span class="p">)</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — narrow to one element </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".item"</span><span class="p">).</span><span class="nx">first</span><span class="p">()).</span><span class="nx">toHaveText</span><span class="p">(</span><span class="s2">"First Item"</span><span class="p">)</span> <span class="c1">// GOOD — assert against an array </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".item"</span><span class="p">)).</span><span class="nx">toHaveText</span><span class="p">([</span><span class="s2">"First Item"</span><span class="p">,</span> <span class="s2">"Second Item"</span><span class="p">,</span> <span class="s2">"Third Item"</span><span class="p">])</span> <span class="c1">// GOOD — use a more specific locator </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">level</span><span class="o">:</span> <span class="mi">1</span> <span class="p">})).</span><span class="nx">toHaveText</span><span class="p">(</span><span class="s2">"Welcome"</span><span class="p">)</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a>, <a href="locators.md">core/locators.md</a></p> <hr> <h3>"Error: page.waitForSelector: Timeout 30000ms exceeded (deprecated)"</h3> <p dir="auto"><strong>Cause</strong>: <code>page.waitForSelector()</code> timed out. This method is deprecated in favor of locator-based assertions.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Using legacy <code>page.waitForSelector()</code> from older tutorials or migration from Puppeteer</li> <li>The selector does not match any element in the DOM</li> <li>The element exists but does not meet the state requirement (e.g., <code>{ state: 'visible' }</code> but element is hidden)</li> </ul> <p dir="auto"><strong>Fix</strong>: Replace <code>waitForSelector()</code> with locator-based assertions.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — deprecated, no auto-retry, not composable </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s2">".loading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">state</span><span class="o">:</span> <span class="s2">"hidden"</span> <span class="p">})</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s2">".content"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">state</span><span class="o">:</span> <span class="s2">"visible"</span> <span class="p">})</span> <span class="c1">// GOOD — locator-based assertions with auto-retry </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".loading"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".content"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="c1">// GOOD — use role-based locators for even more resilience </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"progressbar"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// BAD — deprecated </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s2">".loading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">state</span><span class="o">:</span> <span class="s2">"hidden"</span> <span class="p">})</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s2">".content"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">state</span><span class="o">:</span> <span class="s2">"visible"</span> <span class="p">})</span> <span class="c1">// GOOD — locator-based assertions </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".loading"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">".content"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="c1">// GOOD — role-based locators </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"progressbar"</span><span class="p">)).</span><span class="nx">toBeHidden</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"main"</span><span class="p">)).</span><span class="nx">toBeVisible</span><span class="p">()</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="locators.md">core/locators.md</a>, <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a>, <a href="../migration/from-selenium.md">migration/from-selenium.md</a></p> <hr> <h3>"Error: Test was expected to have a title matching /.../"</h3> <p dir="auto"><strong>Cause</strong>: The <code>expect(page).toHaveTitle()</code> assertion failed because the page title did not match the expected value or pattern within the timeout.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>The page has not finished loading and the title is still the initial empty or default value</li> <li>The SPA updates the document title asynchronously after rendering</li> <li>The expected title has a typo or does not match case-sensitively</li> <li>The page redirected to an error page with a different title</li> </ul> <p dir="auto"><strong>Fix</strong>: Ensure the page is fully loaded and use the correct expected value.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// toHaveTitle auto-retries, but ensure the page has loaded </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="s2">"Dashboard | MyApp"</span><span class="p">)</span> <span class="c1">// Use regex for flexible matching </span><span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="sr">/Dashboard/</span><span class="p">)</span> <span class="c1">// Debug: check what the actual title is </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">title</span><span class="p">())</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/dashboard"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="s2">"Dashboard | MyApp"</span><span class="p">)</span> <span class="c1">// Use regex for flexible matching </span><span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">).</span><span class="nx">toHaveTitle</span><span class="p">(</span><span class="sr">/Dashboard/</span><span class="p">)</span> <span class="c1">// Debug: check what the actual title is </span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">title</span><span class="p">())</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="assertions-and-waiting.md">core/assertions-and-waiting.md</a></p> <hr> <h3>"Error: page.type: Element is not focusable"</h3> <p dir="auto"><strong>Cause</strong>: <code>page.type()</code> or <code>locator.type()</code> could not focus the target element before typing. The element may be hidden, disabled, or not an input.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>The element is covered by an overlay or modal</li> <li>The element has <code>tabindex="-1"</code> and is not natively focusable</li> <li>Using <code>type()</code> instead of <code>fill()</code> — <code>fill()</code> is preferred in almost all cases</li> <li>The element is inside a Shadow DOM and the locator does not pierce it</li> </ul> <p dir="auto"><strong>Fix</strong>: Use <code>fill()</code> instead of <code>type()</code>. Only use <code>type()</code> when you specifically need to simulate individual keystrokes.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — type() is slower and more fragile </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">"#search"</span><span class="p">).</span><span class="kr">type</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span> <span class="c1">// GOOD — fill() clears and sets the value directly </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"searchbox"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span> <span class="c1">// When you truly need keystroke-by-keystroke input (e.g., autocomplete trigger) </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"searchbox"</span><span class="p">).</span><span class="nx">pressSequentially</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">delay</span>: <span class="kt">100</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — fill() is preferred </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"searchbox"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">)</span> <span class="c1">// Keystroke-by-keystroke when needed (e.g., autocomplete) </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"searchbox"</span><span class="p">).</span><span class="nx">pressSequentially</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">delay</span><span class="o">:</span> <span class="mi">100</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="locators.md">core/locators.md</a>, <a href="forms-and-validation.md">core/forms-and-validation.md</a></p> <hr> <h3>"Error: page.setInputFiles: Non-multiple file input can only accept single file"</h3> <p dir="auto"><strong>Cause</strong>: Attempted to upload multiple files to an <code><input type="file"></code> that does not have the <code>multiple</code> attribute.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>Passing an array of files to a single-file upload input</li> <li>The app uses a custom upload component that only handles one file at a time</li> </ul> <p dir="auto"><strong>Fix</strong>: Upload one file at a time, or ensure the input supports multiple files.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript </span> <span class="c1">// BAD — input does not have multiple attribute </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload file"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">([</span> <span class="s2">"file1.pdf"</span><span class="p">,</span> <span class="s2">"file2.pdf"</span><span class="p">,</span> <span class="c1">// error: non-multiple input </span><span class="p">])</span> <span class="c1">// GOOD — single file </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload file"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">(</span><span class="s2">"file1.pdf"</span><span class="p">)</span> <span class="c1">// GOOD — for multiple file input (must have multiple attribute in HTML) </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload files"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">([</span><span class="s2">"file1.pdf"</span><span class="p">,</span> <span class="s2">"file2.pdf"</span><span class="p">])</span> <span class="c1">// To clear the file input </span><span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload file"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">([])</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript </span> <span class="c1">// GOOD — single file </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload file"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">(</span><span class="s2">"file1.pdf"</span><span class="p">)</span> <span class="c1">// GOOD — multiple files (input must have multiple attribute) </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload files"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">([</span><span class="s2">"file1.pdf"</span><span class="p">,</span> <span class="s2">"file2.pdf"</span><span class="p">])</span> <span class="c1">// To clear the file input </span><span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Upload file"</span><span class="p">).</span><span class="nx">setInputFiles</span><span class="p">([])</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="file-operations.md">core/file-operations.md</a>, <a href="file-upload-download.md">core/file-upload-download.md</a></p> <hr> <h3>"Error: browser.newContext: Could not parse content-type application/json"</h3> <p dir="auto"><strong>Cause</strong>: The <code>storageState</code> option was given a path to a file with invalid JSON content, or the file is not a valid Playwright storage state format.</p> <p dir="auto"><strong>Common triggers</strong>:</p> <ul dir="auto"> <li>The auth setup wrote an empty file or incomplete JSON</li> <li>The storage state file was corrupted or truncated</li> <li>Manually editing the storage state file and introducing a syntax error</li> <li>The auth setup failed silently (login did not complete) and saved an invalid state</li> </ul> <p dir="auto"><strong>Fix</strong>: Delete the storage state file and regenerate it.</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-bash display"><span class="c1"># Delete the corrupted file</span> rm -f playwright/.auth/user.json <span class="c1"># Re-run the setup</span> npx playwright <span class="nb">test</span> --project<span class="o">=</span>setup </code></pre></div><p dir="auto">To prevent this, add error checking in the auth setup:</p> <div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-typescript display"><span class="c1">// TypeScript — auth.setup.ts </span><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="kr">as</span> <span class="nx">setup</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"@playwright/test"</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="nx">setup</span><span class="p">(</span><span class="s2">"authenticate"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Password"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"password123"</span><span class="p">)</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Sign in"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Verify login actually succeeded before saving state </span> <span class="k">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Dashboard"</span> <span class="p">})).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">context</span><span class="p">().</span><span class="nx">storageState</span><span class="p">({</span> <span class="nx">path</span>: <span class="kt">authFile</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><div data-attr-class="9I8A3LBz8kFFQXPu:code-block-container code-overflow-scroll"><pre data-attr-class="9I8A3LBz8kFFQXPu:code-block"><code data-attr-class="9I8A3LBz8kFFQXPu:chroma language-javascript display"><span class="c1">// JavaScript — auth.setup.js </span><span class="kr">const</span> <span class="p">{</span> <span class="nx">test</span><span class="o">:</span> <span class="nx">setup</span><span class="p">,</span> <span class="nx">expect</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"@playwright/test"</span><span class="p">)</span> <span class="kr">const</span> <span class="nx">authFile</span> <span class="o">=</span> <span class="s2">"playwright/.auth/user.json"</span> <span class="nx">setup</span><span class="p">(</span><span class="s2">"authenticate"</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Email"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"<a href="mailto:user@example.com" data-markdown-generated-content="">user@example.com</a>"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByLabel</span><span class="p">(</span><span class="s2">"Password"</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">"password123"</span><span class="p">)</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"button"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Sign in"</span> <span class="p">}).</span><span class="nx">click</span><span class="p">()</span> <span class="c1">// Verify login actually succeeded before saving state </span> <span class="kr">await</span> <span class="nx">expect</span><span class="p">(</span><span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">"heading"</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"Dashboard"</span> <span class="p">})).</span><span class="nx">toBeVisible</span><span class="p">()</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">context</span><span class="p">().</span><span class="nx">storageState</span><span class="p">({</span> <span class="nx">path</span><span class="o">:</span> <span class="nx">authFile</span> <span class="p">})</span> <span class="p">})</span> </code></pre></div><p dir="auto"><strong>Related</strong>: <a href="authentication.md">core/authentication.md</a>, <a href="auth-flows.md">core/auth-flows.md</a></p> <hr> <h2>Quick Diagnostic Checklist</h2> <p dir="auto">When you hit an error not listed above, run through this checklist:</p> <ol dir="auto"> <li><strong>Read the full error</strong> — Playwright errors include the locator, action, and a snippet of the page state. The answer is often in the details.</li> <li><strong>Enable traces</strong> — Add <code>trace: 'on'</code> temporarily or <code>trace: 'on-first-retry'</code> in config. Run <code>npx playwright show-trace trace.zip</code> to see screenshots, DOM, network, and console at the point of failure.</li> <li><strong>Use the Playwright Inspector</strong> — Run <code>npx playwright test --debug</code> to step through the test interactively.</li> <li><strong>Check the locator</strong> — Run <code>npx playwright codegen <url></code> to verify locators against the live page.</li> <li><strong>Check the Playwright version</strong> — Run <code>npx playwright --version</code>. Ensure <code>@playwright/test</code> and browser binaries are the same version.</li> <li><strong>Search the Playwright issue tracker</strong> — Many errors have known solutions in <a href="<a href="https://github.com/microsoft/playwright/issues" data-markdown-generated-content="">https://github.com/microsoft/playwright/issues</a>">github.com/microsoft/playwright/issues</a>.</li> </ol> </body></html>