Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

On This Page
Learn how Playwright headless mode works, how to configure it in JS, TS, and Python, run tests on TestMu AI cloud, and debug headless failures effectively.

Nazneen Ahmad
March 31, 2026
Playwright headless mode runs the browser without opening a visible window. All browser operations (navigation, clicks, form interactions, and assertions) execute silently in the background, with no graphical interface displayed. It is Playwright's default behavior: every test runs headless unless you explicitly turn it off.
Headless mode is faster than headed mode, uses significantly less CPU and memory, and runs natively in CI/CD pipelines and server environments where no display is available. When something goes wrong and you need to watch what is happening, switching to headed mode takes a single flag or one config change.
This guide covers the full picture: what playwright headless mode is, how it changed in recent versions, how to configure it in JavaScript, TypeScript, and Python, how to run it on TestMu AI cloud, and how to debug tests when headless makes failures harder to see.
What Does Playwright Headless Mode Mean?
Playwright headless mode runs the browser without opening a visible window. Tests and automation tasks execute in the background, which helps speed up runs, reduce resource use, and fit neatly into CI/CD pipelines.
What Are the Key Aspects of Playwright Headless Mode?
These are the core characteristics that define how headless mode works in Playwright.
What Are the Advantages of Playwright Headless Mode?
Headless mode provides several benefits that improve test performance and scalability.
What Makes Playwright Headless Mode Stand Out?
Playwright headless mode offers unique capabilities that enhance modern test automation workflows.
Playwright headless mode is a browser launch setting that runs the browser engine fully in memory, processing HTML, CSS, JavaScript, and network requests, without rendering anything on screen. From the perspective of the website being tested, there is no difference between headless and headed mode. The browser behaves identically; only the display layer is absent.
Playwright sets headless to true by default for all three supported browsers: Chromium, Firefox, and WebKit. You do not need to configure anything to use headless mode; running your tests is enough.
For a broader look at the category, see our guide on headless browser testing.
Starting with Playwright v1.45, the default Chromium headless mode uses a dedicated chromium-headless-shell binary, a lightweight build of Chromium stripped of the full browser UI layer. This is faster and uses fewer resources than running the full Chrome binary in headless mode.
If your tests require rendering behavior identical to a real Chrome browser, for visual testing, fingerprint-sensitive sites, or specific Chrome APIs not available in the shell, you can opt into using the full Chrome binary by setting channel: 'chromium' in your configuration.
| Mode | Binary | Speed | Use When |
|---|---|---|---|
| Default headless (v1.45+) | chromium-headless-shell | Fastest | CI/CD, large test suites, automation |
| New headless (full Chrome) | Full Chrome binary | Fast | Visual parity, fingerprinting, Chrome-specific APIs |
| Headed mode | Full browser with UI | Slowest | Local debugging, visual validation |
To get started with Playwright automation from scratch, follow our detailed Playwright testing tutorial.
The comparison below covers every factor a team needs to decide which mode to use and when. The benchmark data comes from running the same eCommerce scraper test suite in both modes on identical infrastructure.
| Factor | Headless Mode | Headed Mode |
|---|---|---|
| Speed | Faster, no rendering overhead | Slower, renders full browser UI |
| CPU and memory usage | Significantly lower | Higher |
| CI/CD compatibility | Native, no display server needed | Requires Xvfb or virtual display on Linux |
| Best for debugging | Harder, no visual feedback | Easier, watch tests run in real time |
| Visual validation | Screenshots only | Full visual feedback during run |
| Playwright default | Yes (headless: true) | No, requires --headed flag |
| Benchmark: eCommerce suite avg | 56.21s (min 53.50s, max 60.00s) | 73.77s (min 69.07s, max 85.10s) |
| Speed difference | 24% faster | Baseline |
Note: Run headless in CI/CD and for all automated runs. Switch to headed (---headed) when a test fails and you need to watch what is happening.
Running Playwright tests in headless mode is ideal for most testing scenarios, especially in CI/CD pipelines and large test suites. The performance benefits alone make it the best choice for routine test execution. When you need to debug or visually validate, switching to headed mode is just a flag away.
Playwright is one of the most widely adopted web automation tools today. Unlike older automation testing tools that require a visible browser, Playwright's headless mode lets teams run Playwright automation at scale without any display infrastructure, making it a strong choice for modern automation testing workflows.
Playwright runs in headless mode by default; no configuration needed. The sections below show every method to control headless behavior: CLI, config file, and programmatic launch in both JavaScript and Python.
Before running any tests, make sure you complete the Playwright install step to set up the test runner and required browser binaries. Once installed, you can immediately start executing tests in headless mode using the commands below.
The simplest way to control headless mode is with CLI flags when running tests. This is great for quick local runs or when you want to temporarily switch modes without changing config files.
npx playwright testnpx playwright test --headednpx playwright test --uinpx playwright test --debugnpx playwright test tests/login.spec.ts
npx playwright test --project=chromiumSetting headless in the config file makes behavior consistent for every developer and every CI run. This is the recommended approach for any team project.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html'], ['list']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});export default defineConfig({
use: {
headless: false, // true = headless (default), false = headed
},
});export default defineConfig({
use: {
channel: 'chromium', // opts into full Chrome binary
headless: true,
},
});export default defineConfig({
use: {
headless: !!process.env.CI,
},
});When you need more control over the browser launch process, or when writing scripts outside of the test runner, you can launch browsers programmatically with the Playwright API. This is useful for custom automation tasks, web scraping, or when integrating Playwright into a larger Node.js application.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true }); // default
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
})();const { chromium, firefox, webkit } = require('playwright');
for (const browserType of [chromium, firefox, webkit]) {
const browser = await browserType.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();
}Playwright's Python bindings also support headless mode with the same default behavior. The setup process involves creating a virtual environment, installing the Playwright package, and downloading the browser binaries.
Once set up, you can launch browsers programmatically with the same headless configuration options as in JavaScript.
mkdir playwright_headless_testing
cd playwright_headless_testing
python3 -m venv env
source env/bin/activate # Windows: env\Scripts\activatepip3 install pytest-playwright
playwright install # downloads browser binaries You can use pip for Python 2.x and pip3 for Python 3.x. The pytest-playwright plugin bundles pytest and Playwright together so you can write end-to-end tests directly in Python. Always record installed versions in a requirements.txt for reproducibility.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # default
page = browser.new_page()
page.goto('https://example.com')
print(page.title())
browser.close()browser = p.chromium.launch(headless=False)Note: Always check your project's installed Playwright version with npx playwright --version or pip show playwright. Review the changelog when upgrading from below v1.45.
Structuring your test code with the Page Object Model (POM) pattern makes playwright headless tests easier to maintain and scale. Here is a complete LoginPage example in TypeScript:
// pages/login.page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}Using getByLabel and getByRole instead of CSS selectors makes locators resilient to UI changes and more readable for the whole team.
Running headless tests locally works for development, but for cross-browser coverage, parallel execution at scale, and full test observability across the team, a cloud platform removes infrastructure overhead entirely.
TestMu AI (formerly LambdaTest) is an AI-native automation testing platform that allows you to run automated Playwright tests online. As one of the most comprehensive automation testing tools available, it provides real browsers (Chrome, Firefox, Edge, and WebKit) on demand for web automation at any scale, without requiring any local browser setup.
export LT_USERNAME=your_username
export LT_ACCESS_KEY=your_access_key// lambdatest-setup.ts
import { chromium } from 'playwright';
const capabilities = {
browserName: 'Chrome',
browserVersion: 'latest',
'LT:Options': {
platform: 'Windows 11',
build: 'Playwright Headless Build',
name: 'Playwright Headless Test',
user: process.env.LT_USERNAME,
accessKey: process.env.LT_ACCESS_KEY,
network: true,
video: true,
console: true,
headless: true,
},
};
const browser = await chromium.connect({
wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`,
});
const context = await browser.newContext();
const page = await context.newPage();// Add to projects array in playwright.config.ts:
{
name: 'chrome:latest:Windows 11@lambdatest',
use: { viewport: { width: 1920, height: 1080 } },
},
{
name: 'MicrosoftEdge:latest:macOS Sonoma@lambdatest',
use: { viewport: { width: 1920, height: 1080 } },
},npx playwright test --project="chrome:latest:Windows 11@lambdatest"// In afterEach or test teardown:
await page.evaluate((_) => {},
`lambdatest_action: ${JSON.stringify({
action: 'setTestStatus',
arguments: {
status: testInfo.status,
remark: testInfo.error?.message || 'OK',
},
})}`
);For more details on running Playwright tests on TestMu AI, see our documentation on Playwright testing on TestMu AI.
TestMu AI publishes an open-source Playwright Skill for AI coding agents (Claude Code, Cursor, Gemini CLI, and others).
The skill teaches any AI agent how to write, configure, and run Playwright automation tests correctly, including cloud execution on TestMu AI, without hallucinating APIs or missing setup steps.
It is built specifically for teams who use AI assistants to accelerate their automation testing workflows with Playwright.
The Playwright Skill is a SKILL.md-based instruction set that AI agents load on demand when you ask them to write or run Playwright tests. It covers:
To get started with the Playwright Skill, check out the Playwright Skill repository.
This example demonstrates playwright headless testing with a real-world use case: scraping product data from the TestMu AI eCommerce Playground. The test suite validates that the scraper correctly extracts product titles, prices, and image links across 15 product listings using the Page Object Model pattern.
Test Scenario:
The test suite is designed to run in headless mode, demonstrating how to structure and execute Playwright tests without a browser UI while still validating complex interactions and data extraction.
playwright_headless_testing/
├── conftest.py # fixtures and cloud connection
├── .env # credentials (gitignored)
├── pages/
│ └── ecommerce_scraper.py # Page Object Model
└── tests/
└── test_ecommerce_scraper.py# pages/ecommerce_scraper.py
import time
class EcommerceScraper:
BASE_URL = 'https://ecommerce-playground.lambdatest.io/'
def __init__(self, page):
self.page = page
self.page.goto(self.BASE_URL)
def select_product_category(self):
self.page.get_by_role('button', name='Shop by Category').click()
self.page.wait_for_load_state('networkidle')
self.page.get_by_role('link', name='Laptops & Notebooks').click()
self.page.wait_for_load_state('domcontentloaded')
def scroll_down_page(self):
self.page.mouse.wheel(0, 3000)
time.sleep(1) # allow images to load after scroll
def product_title_grid_list(self):
self.page.wait_for_load_state('domcontentloaded')
return (
self.page.locator('#entry_212408 div')
.locator('.product-grid div')
.locator('.caption')
.locator('.title')
.all_inner_texts()
)
def product_price_grid_list(self):
self.page.wait_for_load_state('domcontentloaded')
return (
self.page.locator('#entry_212408 div')
.locator('.product-grid div')
.locator('.caption')
.locator('.price')
.all_inner_texts()
)
def product_image_grid_list(self):
self.page.wait_for_load_state('domcontentloaded')
self.scroll_down_page()
images = (
self.page.locator('#entry_212408 div')
.locator('.product-grid div')
.locator('.product-thumb-top')
.locator('.image .carousel-inner')
.locator('.active > img')
)
return [img.get_attribute('src') for img in images.all()]
def display_scraped_product_details(self):
titles = self.product_title_grid_list()
prices = self.product_price_grid_list()
images = self.product_image_grid_list()
return [
{'title': t, 'price': p, 'image': i}
for t, p, i in zip(titles, prices, images)
]# tests/test_ecommerce_scraper.py
import pytest
from pages.ecommerce_scraper import EcommerceScraper
def test_product_title_list(page):
scraper = EcommerceScraper(page)
scraper.select_product_category()
titles = scraper.product_title_grid_list()
assert isinstance(titles, list), 'Expected a list of product titles'
assert len(titles) == 15, f'Expected 15 titles, got {len(titles)}'
def test_product_price_list(page):
scraper = EcommerceScraper(page)
scraper.select_product_category()
prices = scraper.product_price_grid_list()
assert isinstance(prices, list), 'Expected a list of product prices'
assert len(prices) == 15, f'Expected 15 prices, got {len(prices)}'
def test_product_image_link_list(page):
scraper = EcommerceScraper(page)
scraper.select_product_category()
images = scraper.product_image_grid_list()
assert isinstance(images, list), 'Expected a list of image URLs'
assert len(images) == 15, f'Expected 15 image links, got {len(images)}'
def test_product_detail_list(page):
scraper = EcommerceScraper(page)
scraper.select_product_category()
details = scraper.display_scraped_product_details()
assert isinstance(details, list), 'Expected a list of product dicts'
assert len(details) == 15, f'Expected 15 products, got {len(details)}'
assert all(isinstance(d, dict) for d in details), 'Each item must be a dict'
assert all('title' in d and 'price' in d and 'image' in d for d in details)# Run headless (default, no flag needed)
pytest -s tests/test_ecommerce_scraper.py
# Benchmark: compare headless vs. headed execution time
hyperfine "pytest tests/test_ecommerce_scraper.py" -w 2The eCommerce scraper test suite was run in both modes using hyperfine, a command-line benchmarking tool. Results below are from the same machine, same network conditions, with 2 warm-up runs before measurement.
| Metric | Headed Mode | Headless Mode | Difference |
|---|---|---|---|
| Average execution time | 73.77 seconds | 56.21 seconds | -17.56s (24% faster) |
| Minimum time | 69.07 seconds | 53.50 seconds | -15.57s |
| Maximum time | 85.10 seconds | 60.00 seconds | -25.10s |
| Variance | Higher (rendering variability) | Lower (consistent) | More predictable in headless |
A 24% speed improvement on a single test file compounds across large suites. A 500-test suite that averages 6 hours headed could finish in under 4.5 hours headless, using the same infrastructure running in parallel.
Note: Run Playwright headless tests on automation cloud. Try TestMu AI Now!
Headless mode is built for CI/CD. No virtual display server (Xvfb) is needed; the browser runs in memory on any Linux runner. The workflow below is production-ready and runs tests on every push and pull request.
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests (headless by default)
run: npx playwright test
env:
CI: true
- name: Upload test report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30Note: Set headless: !!process.env.CI in playwright.config.ts and this workflow automatically runs headless in GitHub Actions while staying headed on local machines.
When a test fails in headless mode, the lack of visual feedback makes diagnosis harder. Playwright provides several tools that let you reconstruct exactly what happened without needing to reproduce the failure visually.
npx playwright test --headednpx playwright test --debug// playwright.config.ts
export default defineConfig({
use: {
screenshot: 'only-on-failure', // saves screenshot on test failure
video: 'retain-on-failure', // saves video replay on test failure
},
});// In your test or beforeEach fixture:
await context.tracing.start({ screenshots: true, snapshots: true });
// ... run test steps ...
await context.tracing.stop({ path: 'trace.zip' });# View the trace locally:
npx playwright show-trace trace.zippage.on('console', msg => console.log('Browser console:', msg.text()));
page.on('pageerror', err => console.error('Page error:', err.message));The combination of these tools gives you a powerful toolkit for diagnosing and fixing issues in headless tests without needing to reproduce them in headed mode.
Use any of the methods below to run with a visible browser window.
| Method | How to Disable Headless | When to Use |
|---|---|---|
| CLI flag | npx playwright test --headed | Quick toggle for a single run |
| playwright.config.ts | use: { headless: false } | Consistent headed mode for all team members |
| JavaScript launch | chromium.launch({ headless: false }) | Programmatic control per test or suite |
| Python launch | p.chromium.launch(headless=False) | Python test scripts |
| Environment variable | headless: !process.env.CI | Headed locally, headless in CI automatically |
Playwright’s headless mode is the default because it fits how modern testing actually runs—in CI/CD pipelines, containers, and cloud environments where speed and efficiency matter most. By removing the browser UI, tests execute faster and with fewer dependencies, making setups simpler and more reliable.
The performance gains, including up to a 24% speed improvement and no need for a display server, make headless mode the practical choice for scaling automated test suites. It reduces resource usage while maintaining consistency across different environments, which is essential for stable test execution.
With the right setup and tooling, teams can easily balance local debugging with automated headless runs. When combined with platforms like TestMu AI, it allows seamless scaling across browsers and devices without managing infrastructure, making headless testing both efficient and future-ready.
Did you find this page helpful?
More Related Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance