How to fix "Target page, context or browser has been closed" in Playwright?
PlaywrightThe 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.