In Playwright, what does the headless option on browserType.launch() control, and what is its default?
answer
- One switch, decided when the browser starts
- The quiet default nobody sets
- Chromium has two builds, not one
- Pair it with slowMo to watch
- channel chromium selects new headless mode
basics
~20 sPlaywright's headless launch option decides whether the browser draws a visible window. It defaults to true, so launch() starts an invisible browser; passing headless: false opens a real window you can watch a failing flow in.
solid answer
~40 s`headless` is an option on `browserType.launch()` (and `launchPersistentContext`) that says whether the browser process draws a window. It defaults to `true` in Playwright 1.63, so `await chromium.launch()` runs invisibly; `{ headless: false }` starts a visible browser. It is a process-level decision fixed at launch: every context and page from that browser inherits it. Headed and headless runs use the same automation protocol, so locators, screenshots and video capture behave identically. One Chromium subtlety matters: with `headless: true` and no `channel`, Playwright runs the separate headless shell build, while `channel: 'chromium'` opts into Chromium's new headless mode, which is the real browser without a window. Headed mode is a debugging tool - pair it with `slowMo` locally and keep it out of CI, where there is usually no display.
code
typescript · 9 linesimport { chromium } from '@playwright/test';
const browser = await chromium.launch({
headless: !!process.env.CI,
slowMo: process.env.CI ? 0 : 250,
});
const page = await browser.newPage();
await page.goto('https://staging.example.com/hotels/search');
await browser.close();go deeper
Remember the default is true and that headless: false is how you watch a run. Know that it belongs in the launch options, not in the test body.
Be able to explain that the switch is fixed per browser process and inherited by every context, and that Chromium runs a separate headless shell build unless a channel is set.
Show the judgment of keeping headed mode out of committed code, and of treating a headless-only failure as a race in the test rather than as a defect in headless mode.
Own the call on whether the suite runs against the headless shell for speed or the real browser through channel chromium for fidelity, and say what evidence would change that choice.
`headless` is one of the options accepted by `browserType.launch()` - the call behind `chromium.launch()`, `firefox.launch()` and `webkit.launch()`. It answers a single question: should this browser process draw a window on screen? In Playwright 1.63 it defaults to `true`, so a bare `await chromium.launch()` gives you an invisible browser, and `await chromium.launch({ headless: false })` gives you one you can watch. ## What the option actually switches - It is a **process-level** decision, fixed the moment the browser starts. Every context and page created from that browser inherits it - you cannot have one page headed and another headless inside the same browser process. - Headed and headless browsers speak the same automation protocol, so `page.goto()`, locators, screenshots and video capture behave the same way in both. - Turning it off is a debugging move: you watch the hotel-booking search page mis-render instead of reading about it in a stack trace. - Because it is a launch option, changing it means starting a new browser. A running test cannot toggle it. ## Chromium quietly runs two different builds This is the part interviewers actually probe. - With `headless: true` and **no** `channel`, Chromium runs the separate **headless shell** build that Playwright ships next to the full browser. It starts fast and is cheap on CI, but it is not the binary your users run. - With `channel: 'chromium'` you opt into Chromium's **new headless mode**: the real browser binary, running without a window. It is more faithful to production behaviour, and it is what lets browser extensions work in a headless run. - The branded channels - `'chrome'`, `'msedge'` and their beta, dev and canary variants - also run a real browser, headed or headless. | Launch options | What actually starts | Typical use | |---|---|---| | `{}` (defaults) | Chromium headless shell | fast, invisible CI runs | | `{ headless: false }` | Full Chromium, visible window | watching a failure locally | | `{ channel: 'chromium' }` | Real Chromium, new headless mode | closest-to-users headless run | Firefox and WebKit have no such split: one binary, with or without a window. ## What headless does not change - It does not change timeouts, auto-waiting or actionability rules. A test that loses a race headless loses it headed too, just less often. - It does not change the viewport. Size comes from context options, not from this switch. - It does not disable screenshots, videos or downloads - all of them work in an invisible browser. - It does not slow anything down for the benefit of your eyes. Headless or not, operations run as fast as the machine allows unless you also pass `slowMo`. ## Watching a run properly 1. Launch headed: `chromium.launch({ headless: false })`. 2. Add `slowMo: 250` so each operation is visible instead of a blur. 3. Reproduce the failing hotel-booking flow - room search, room detail, guest profile. 4. Put both options back behind an environment check before committing; a headed CI job either fails for want of a display or burns machine time no one watches. ## Traps worth naming - **Hardcoding `headless: false` in shared code.** Most CI machines have no display server, so the run dies as an infrastructure failure dressed up as a test failure. - **Expecting `headless: 'new'`.** That spelling belongs to another tool. In Playwright the option is a boolean, and the new headless mode is selected through `channel: 'chromium'`. - **Blaming headless for a flake.** Headless is a rendering decision, not a timing one. If the suite only passes headed, you have found a race in the test, not a bug in headless mode. - **Assuming headless means no browser.** A real browser process starts, consumes real memory, and needs the same system libraries as a headed one. The practical summary: leave the default alone for automated runs, flip it to `false` while you are looking at something with your own eyes, and reach for `channel: 'chromium'` when the headless shell is not a faithful enough stand-in for the browser your guests actually book rooms in.
- How would you make a single local run headed without editing the code that calls launch()?Read the flag from the environment at the launch call - `headless: process.env.HEADED !== '1'` - so one shell variable flips it. The launch options stay committed as headless, and nobody has to remember to revert a debugging edit before pushing.
- A test passes headed and fails headless on the same machine. Where do you look first?At timing, not at rendering. Headed runs are slower, which hides races, so look for an action that fires before the app is ready and replace it with a wait on a real readiness signal. Also check whether the headless shell differs from the real browser by retrying with `channel: 'chromium'`.
saying these in an interview costs you the question
- Says headless defaults to false and must be enabled
- Writes headless: 'new' as if it were a string option
- Claims screenshots and video need a headed browser
- Commits headless: false so CI matches local runs
- Treats headless mode as the cause of flaky tests