How to Run Playwright Tests in Headed Mode
Run Playwright tests with a visible browser using --headed, persistent config, debug and UI modes, Python pytest, CI displays, and fixes for common errors.
To run JavaScript or TypeScript Playwright tests with a visible browser, execute:
npx playwright test --headed
Playwright runs headless by default. The --headed flag opens the browser so you can watch how each test interacts with the page. The same flag can be combined with a test file, title filter, or project selector. See the official Playwright running and debugging guide for the runner options.
1. Run a headed Playwright test
Run the complete test suite
npx playwright test --headed
With other package managers, use the equivalent command:
yarn playwright test --headed
pnpm exec playwright test --headed
Run one test file
npx playwright test tests/login.spec.ts --headed
Run a test by title
npx playwright test --headed -g "successful login"
Run one configured browser project
npx playwright test --headed --project=chromium
npx playwright test --headed --project=firefox
npx playwright test --headed --project=webkit
The project name must match a project in your playwright.config file.
Pass additional arguments
Put the test path and filters before or after --headed; Playwright accepts both forms:
npx playwright test tests/cart.spec.ts -g "adds an item" --project=chromium --headed
2. A complete headed test example
Install Playwright and its browser binaries if the project is new:
npm init playwright@latest
Create tests/example.spec.ts:
import { test, expect } from '@playwright/test';
test('home page has the expected title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
await expect(page.getByRole('link', { name: 'Get started' })).toBeVisible();
});
Run it visibly:
npx playwright test tests/example.spec.ts --headed
The browser window is controlled by the test runner. Avoid clicking it manually while a test is running because manual input can race with automated actions.
3. Make headed mode the default
For a persistent setting, configure headless: false. The documented default is true.
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
headless: false,
},
});
Now this command opens the browser without needing the flag:
npx playwright test
A command-line option is useful for a one-off investigation; configuration is useful when a local project is normally watched interactively. Keep CI configuration separate if your build agents do not have a display.
4. Choose between headed, debug and UI modes
| Workflow | Command | What you get | Best use |
|---|---|---|---|
| Headed run | npx playwright test --headed |
Normal test execution with a visible browser | Watch a complete run or reproduce a visual problem |
| Debug mode | npx playwright test --debug |
Browser plus Playwright Inspector, step controls and locator exploration | Pause and inspect one action at a time |
| UI Mode | npx playwright test --ui |
Interactive test selection, watch mode, traces and per-action details | Explore a suite while developing tests |
Use debug mode for step-through inspection
npx playwright test tests/login.spec.ts --debug
Debug mode launches headed browsers, runs tests one by one, opens the Inspector and sets the default timeout to zero. That makes it useful for finding a bad locator or observing state immediately before an action.
Use UI Mode for interactive exploration
npx playwright test --ui
UI Mode lets you select tests, watch changes, inspect traces and review information for each action. In a container, the documented form is:
npx playwright test --ui --ui-host=0.0.0.0 --ui-port=9323
Only bind UI Mode to a network interface when you understand who can reach it. Playwright warns that remotely exposed UI Mode can reveal traces, passwords and other secrets to machines on the network. Prefer localhost or a protected tunnel.
5. Python Playwright tests with pytest
The Python pytest plugin has its own command-line interface. Use pytest --headed:
pytest --headed
You can choose a browser as well:
pytest --browser chromium --headed
pytest --browser firefox --headed
pytest --browser webkit --headed
Example test:
from playwright.sync_api import Page, expect
def test_home_page(page: Page):
page.goto("https://playwright.dev/")
expect(page).to_have_title("Playwright")
The pytest options apply to the plugin’s default browser, context and page fixtures. They do not automatically change browser, context or page objects that your code creates directly through the Playwright API.
6. Headed mode in Linux CI
A headed browser needs a display. Playwright’s CI guidance says Linux agents require Xvfb, a virtual X server. Run the suite through it:
xvfb-run npx playwright test
For a visibly headed test inside the virtual display:
xvfb-run npx playwright test --headed
Check that your CI image contains Xvfb and Playwright’s browser dependencies. A command can fail even when Playwright itself is installed if the image lacks the display server or shared libraries.
CI checklist
- Install the browser binaries with
npx playwright install --with-depswhere your image requires it. - Install Xvfb on Linux workers that execute headed tests.
- Use a fixed viewport and timezone when visual output must be repeatable.
- Keep UI Mode bound to localhost unless access is authenticated and deliberately restricted.
- Capture traces, screenshots or videos only when needed because they increase storage and run time.
7. Make headed runs easier to inspect
Pause at a deliberate point
import { test } from '@playwright/test';
test('inspect the checkout', async ({ page }) => {
await page.goto('https://example.com/checkout');
await page.pause();
});
Run this test with --debug to open the Inspector at the pause point.
Slow actions for visual observation
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
headless: false,
launchOptions: {
slowMo: 150,
},
},
});
slowMo deliberately adds delay to browser operations. Use it while diagnosing a sequence, then remove it: it increases test duration and can hide timing problems.
Keep browser state consistent
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
headless: false,
viewport: { width: 1280, height: 800 },
timezoneId: 'UTC',
locale: 'en-US',
},
});
A fixed viewport, locale and timezone reduce visual differences between runs. Use a separate browser context per test when isolation matters.
8. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
npx playwright test --headed says the command is unknown |
The project does not have Playwright Test installed, or the command is run outside the project | Install @playwright/test, run from the project root, then use npx playwright install. |
| Browser does not appear locally | The test exits immediately, a project is still headless, or the command is running in a container without a visible display | Confirm --headed is present, add --debug or page.pause(), and use Xvfb in Linux containers. |
Missing X server or DISPLAY error |
Linux headed execution has no display server | Install and run through Xvfb: xvfb-run npx playwright test --headed. |
| Browser executable is missing | Playwright package is installed but browser binaries are not | Run npx playwright install; on supported Linux images use npx playwright install --with-deps. |
| Tests time out only in headed mode | Visible rendering is slower, an animation changes layout, or a locator depends on timing | Use locator assertions and auto-waiting, inspect with --debug, and fix the synchronization issue instead of adding a large global delay. |
Python reports an unrecognized --headed option |
The Playwright pytest plugin is not installed or another pytest plugin is being invoked | Install pytest-playwright, verify with pytest --help, and run the command from the correct environment. |
| Python option does not affect a manually created page | CLI options configure default fixtures only | Pass headless=False when launching your manually created browser. |
| UI Mode exposes sensitive data | UI Mode is bound to 0.0.0.0 and reachable by other machines |
Use localhost, an authenticated tunnel or a private network. Do not expose traces containing credentials. |
| Headed tests are flaky in CI but stable locally | Different fonts, display size, CPU limits, timezone or network conditions | Standardize the image and environment, set viewport and timezone explicitly, and collect a trace for the failing retry. |
9. Performance, reliability and cost considerations
- Runtime: headed rendering and optional slow motion add work. Use headed mode for diagnosis and keep routine CI runs headless unless a visible browser is required.
- Parallelism: several headed workers can consume substantial CPU, memory and display resources. Reduce workers when the machine becomes saturated.
- Reliability: a headed run does not make a test more reliable. Stable locators, explicit expectations and deterministic test data matter more than whether a window is visible.
- CI display: Xvfb provides a virtual display; it does not make a browser visible on your physical desktop. Save screenshots, videos or traces when you need artifacts from CI.
- Cost: the Playwright CLI flag itself has no service charge. Your costs are the machine time, CI minutes and any artifact storage used by the run.
10. Or skip the browser setup
If your goal is a clean image of a web page rather than interactive test debugging, ScreenshotNeo returns a screenshot from one HTTP request. Its API accepts PNG, JPEG or WebP output, and its options cover full-page capture, selectors, devices, dark mode, custom CSS and JavaScript, waits, headers, cookies, blocking rules and more. Read the ScreenshotNeo API documentation for the complete parameter list.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
11. FAQ
Does headed mode change the test assertions?
No. It changes browser visibility. The same test code, assertions and Playwright locators run unless your application behaves differently because of viewport, timing or environment.
Can I run headed mode for only one project?
Yes. Combine the flag with a project selector, for example npx playwright test --project=chromium --headed.
Should CI always use headed mode?
No. Use it when a visible browser helps diagnose a failure or when the test specifically requires headed execution. A normal headless run is usually simpler for unattended CI.
Is --debug the same as --headed?
No. --headed only shows the browser. --debug also opens Inspector controls and changes execution for step-through debugging.
Why can’t I see a browser when using Xvfb?
Xvfb is a virtual display in the CI process. It satisfies the display requirement but does not forward the window to your local desktop. Use test artifacts or a remote desktop setup if you need to view it.


