How do I run Playwright in headless vs headed mode?
PlaywrightPlaywright runs headless by default — the browser launches without a visible UI, which is faster and what CI environments use. Headed mode opens a real, visible browser window so you can watch the test execute. You can switch between them with a CLI flag, a config setting, or a per-test override, depending on whether you want the change to apply to one run, every local run, or one specific test.
Run headed from the command line
The fastest way to see a test execute is the --headed flag, which overrides whatever the config says for that run only:
npx playwright test --headed
Run a single spec headed:
npx playwright test login.spec.ts --headed
Combine with --debug to also pause at each step in the Playwright Inspector:
npx playwright test login.spec.ts --headed --debug
Set the default in config
To make headed mode the default for local development (while CI still runs headless), set use.headless in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
headless: !!process.env.CI,
},
});
This runs headed locally and headless in CI without needing --headed on every command. Setting headless: false unconditionally works too, but then CI runs headed as well, which usually fails or requires a virtual display (xvfb) — most teams key it off process.env.CI instead.
Override per test or per project
For a single test file that needs a visible browser regardless of the global config, use test.use():
import { test, expect } from '@playwright/test';
test.use({ headless: false });
test('watch this one visually', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
Or scope it to a whole project in the config, useful for a dedicated "debug" project alongside your normal headless one:
export default defineConfig({
projects: [
{ name: 'chromium-headless', use: { headless: true } },
{ name: 'chromium-headed', use: { headless: false } },
],
});
Run just that project with npx playwright test --project=chromium-headed.
Launch options vs use.headless
headless is also settable inside launchOptions when you're calling chromium.launch() directly (outside the test runner's fixtures), rather than through use:
import { chromium } from '@playwright/test';
const browser = await chromium.launch({ headless: false });
Inside playwright.config.ts, prefer use: { headless: false } — it's what the test runner's fixtures read. launchOptions.headless matters when you're scripting Playwright standalone, not through @playwright/test.
Why it works
Headless and headed launch the same browser engine; the difference is whether a compositor renders to an actual window. Headless is preferred for CI because it's lighter and needs no display server. Headed is preferred for local debugging because you can watch hover states, animations, and dialogs happen in real time instead of reading them back from a trace. Neither mode changes what the page or your test code sees — page.evaluate(), selectors, and assertions behave identically in both. The one place behavior can genuinely diverge is rendering-dependent timing (animations, focus, fonts) — see the failures article linked below if a test passes headed but fails headless.
Tips
- Use --headed --debug together when you need both a visible browser and step-by-step pausing.
- Key headless off process.env.CI in config so you don't have to remember --headed locally and don't accidentally leave headed mode on for CI.
- A dedicated chromium-headed project is handy when you want a one-off visual run without touching the default config.
- If a test only fails in headless mode (not just runs differently, but actually fails), that's a different problem — see how to fix Playwright tests that fail only in headless mode.