skip to content

In Selenium, why does a headless run render a responsive page at a different layout than your headed run?

level: middleimportance: should knowfreq 58%

answer

  1. Two machines, two different window sizes
  2. No desktop means no window manager
  3. The browser picks a built-in default
  4. Chrome headless starts around 800 wide
  5. Launch argument versus a later resize

basics

~20 s

A headless browser has no desktop to size a window from, so it opens at a small fixed default - 800 by 600 in Chrome - and the page picks its narrow layout. Set the width explicitly at launch.

solid answer

~40 s

A responsive page chooses its layout from the CSS viewport width, and that width comes from the browser window. In a headed run the desktop's window manager sizes that window near your monitor; in a headless run there is no desktop, so the browser opens an off-screen window at a fixed built-in default - historically `800x600` in Chrome. Selenium 4 does not override that for you. The fix is to make the width an explicit input of the suite: pass `--window-size=1600,1200` to Chrome or Edge at launch so the very first paint is at the target width, or call `driver.manage().window().setSize(new Dimension(1600, 1200))` once the session exists. Verify rather than assume - read `window.innerWidth` from the page and fail loudly if it is not what the suite claims to test.

code

java · 7 lines
java
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new", "--window-size=1600,1200");
WebDriver driver = new ChromeDriver(options);
driver.get("https://warehouse.example.com/counts/2026-09/aisle-14");
long viewport = (long) ((JavascriptExecutor) driver).executeScript("return window.innerWidth;");
System.out.println("stock-count grid laid out at " + viewport + " CSS px");
driver.quit();

go deeper

for a junior

Be ready to say that a headless browser starts at a fixed default size, not at your screen size, and to name the two ways to change it: a launch argument, or a resize call on the window.

for a middle

Explain the mechanism: layout follows the CSS viewport, the viewport follows the window, and with no window manager the browser falls back to a built-in default. Know that the launch argument applies before the first paint and a resize applies after.

for a senior

Show how you diagnose it from the symptom rather than the cause: a locator failure with a correct locator, a screenshot of an unfamiliar layout. Then make the width an asserted input of the suite so the failure never reappears silently.

for a principal

Own the position that viewport width is configuration, not incident. Decide where that single value lives, how a run proves it got it, and how you keep local debugging and pipeline runs on the same layout without duplicating the setting in every suite.

## Why the two runs disagree A **responsive page** picks its layout from the **CSS viewport width** the browser reports, never from the machine the test runs on. That viewport comes from the browser window. In a **headed** run the operating system's **window manager** creates a real window and sizes it - typically somewhere near your monitor, and often larger still if something in your setup maximised it. In a **headless** run there is no desktop and no window manager at all, so the browser opens its own off-screen window at a **fixed built-in default** and lays the page out there. Nothing in Selenium 4 changes that default on your behalf. The client asks the remote end for a session, the browser decides how big its window is, and the page reflows accordingly. - In Chrome, including `--headless=new`, the headless default has long been **800 x 600**, whatever monitor is attached. - Firefox likewise starts headless at its own fixed default rather than at the screen size. - The number belongs to the **browser**, not to Selenium, so it can move between browser versions - which is precisely why a suite must not lean on it. - Continuous-integration machines usually have no display server either, so "it works on my machine" and "it works in the pipeline" are two different viewport widths. ## What it costs a warehouse stock-count grid Take a stock-count grid that lists one row per bin with the columns SKU, bin, expected, counted and variance while the viewport is at least 1100 px wide, and collapses to one stacked card per bin below that, with variance tucked behind a toggle. Headed at 1600 px your run sees the table. Headless at 800 px it sees cards. What reaches you is a `NoSuchElementException` on the variance cell. That message says nothing whatsoever about width, the stack trace points at a locator that is perfectly correct, and the failure screenshot shows a rendering of the count sheet nobody on the team has ever looked at. Engineers routinely spend an afternoon on this before someone thinks to print the viewport. ## Two places to set the size, and they are not equivalent | | `--window-size=1600,1200` at launch | `manage().window().setSize(...)` afterwards | |---|---|---| | When it applies | before the browser paints anything | after the session exists, usually after a page has loaded | | What the page sees | its first and only layout, already at the target | a **resize**: the page reflows and a `resize` event fires | | Typical failure | the argument's spelling is browser-specific | anything computed at load already used the old width | | Where it lives | the browser options passed at session creation | test setup code, once per session | The launch argument is browser-specific: Chrome and Edge take `--window-size=W,H`, while Firefox takes separate `--width` and `--height` arguments. The Selenium call is the portable one, because it goes through the W3C **Set Window Rect** command that every conforming driver implements. The practical difference is *when*. A stock-count grid that measures its own container in a load handler and caches a column count will keep the narrow decision after a later resize, because the measurement already ran. A grid driven purely by CSS media queries reflows happily either way. ## Making the width an explicit input 1. Decide the one width the suite claims to test, and write it down in a single place rather than in each test. 2. Apply it **at launch**, so the first paint is already correct and no load-time measurement sees the default. 3. Assert it once at the start of the run by reading the real viewport, and fail with a clear message if it is wrong. 4. Use the same width when debugging headed, so what you watch and what CI runs are the same layout. ## Confirming what the page actually got Two different numbers are available, and mixing them up is the second-most-common version of this bug: - `driver.manage().window().getSize()` returns a `Dimension` describing the **whole browser window**, chrome included. - Executing `return window.innerWidth;` in the page returns the **CSS viewport** the layout actually used. ```java long viewport = (long) ((JavascriptExecutor) driver) .executeScript("return window.innerWidth;"); if (viewport < 1100) { throw new IllegalStateException("stock-count grid ran at " + viewport + " px"); } ``` One assertion like that turns a mystifying locator failure into a one-line explanation, in every future run.

  • The grid is set to 1600 px at launch and still collapses on one CI agent. Where do you look next?
    Read `window.innerWidth` from the page rather than trusting the launch argument. A misspelled or browser-wrong argument is silently ignored, so the window may still be at the default. Check the browser is the one you think it is, since the argument spelling differs between Chrome and Firefox, and confirm nothing later in setup resizes the window again.
  • Does resizing after the page has loaded always give the same rendering as launching at that size?
    Not always. CSS media queries re-evaluate on resize, so pure-CSS layouts match. Anything that measured its container in JavaScript at load and cached the result - a grid that computed how many columns fit - keeps the old decision unless it listens for the `resize` event. Setting the size at launch avoids the whole class of difference.
  • Should the suite maximise the window instead of setting an explicit size?
    No. `maximize()` produces whatever the host's screen gives it, so the width differs between your laptop, a colleague's monitor and a CI agent, and the run stops being reproducible. On a headless machine there may be no window manager to maximise against at all. An explicit width is a stated input; a maximised window is an accident of hardware.

saying these in an interview costs you the question

  • Claims headless uses the same window size as a headed run
  • Thinks headless disables CSS media queries entirely
  • Believes headless sends a mobile user-agent, so the phone layout appears
  • Assumes Selenium sets a sensible default window size for you
  • Calls maximize in CI and treats the result as a known width