How to fix "Target page, context or browser has been closed" in Playwright?

Playwright

The error "Target page, context or browser has been closed" surfaces when Playwright tries to interact with a page, frame, or locator after the owning browser context or browser has already been shut down. It commonly appears as Error: locator.click: Target page, context or browser has been closed in test output, and a frequent source is calling browserContext.close() which silently closes every page it owns. AI agent frameworks like browser_use that manage their own browser lifecycle can also trigger this when they close the context before all async interactions have settled.

Common mistake

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

await page.goto('https://app.example.com/dashboard');

// Closes context AND all its pages immediately
await context.close();

// page is now closed — this throws
await page.getByRole('button', { name: 'Export' }).click();

A subtler version fires when cleanup runs inside a Promise.all and one branch closes the context while another is still acting on a page from that context.

The fix

import { chromium } from '@playwright/test';

async function runExport() {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  try {
    await page.goto('https://app.example.com/dashboard');
    await page.getByRole('button', { name: 'Export' }).click();

    const downloadPromise = page.waitForEvent('download');
    await page.getByRole('button', { name: 'Confirm export' }).click();
    const download = await downloadPromise;
    await download.saveAs('./exports/report.csv');
  } finally {
    // Close only after all awaited work is done
    await context.close();
    await browser.close();
  }
}

When using Playwright Test fixtures, cleanup is handled automatically — you should never need to call browser.close() manually inside a test body:

import { test, expect } from '@playwright/test';

test('exports report', async ({ page }) => {
  await page.goto('/dashboard');
  // page and context are cleaned up by the fixture after the test
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Why it works

The Playwright browser context owns all pages it creates, so context.close() immediately invalidates every page reference from that context. Keeping all page interactions inside a try/finally block before closing ensures teardown only runs once all awaited operations have resolved. When using test fixtures, Playwright's built-in lifecycle management handles ordering correctly, removing the possibility of early teardown.

Tips

  • Never fire-and-forget async page operations — unresolved promises that access the page after cleanup is a common source of this error in automation scripts.
  • If you're using Promise.all to parallelize page actions and teardown, structure teardown as a sequential step after the parallel block resolves.
  • In browser_use or similar agent frameworks, confirm the framework's context lifecycle is compatible with your post-action assertions before the session ends.
  • If the error appears only in CI and is related to browser crashes rather than early teardown, see how to fix Playwright target closed after crashes for the crash-specific diagnosis path.