skip to content

In Playwright, what does mounting in a real browser engine catch that a simulated DOM cannot?

level: middleimportance: must knowfreq 62%

answer

  1. Not a simulation of a browser
  2. Layout is computed, not assumed
  3. Clipping and overlap need real geometry
  4. Zero-sized boxes in a simulated DOM
  5. Engines to install, build step, slower runs

basics

~20 s

A Playwright component test renders in Chromium, Firefox or WebKit, so real CSS, real layout and real hit-testing apply. A simulated DOM in Node computes no boxes, so clipping, overlap and interception bugs stay invisible there.

solid answer

~50 s

A mounted run puts the component in one of the engines Playwright ships and drives it with the same `Locator` API an end-to-end test uses. Stylesheets cascade, the layout engine computes boxes, fonts change metrics, and a click is dispatched at a coordinate after a real hit-test. That is why geometric bugs surface: a toast covering a date picker's confirm button makes `locator.click()` fail with an interception error, and `expect(locator).toBeVisible()` reads computed style and box size rather than guessing. A simulated DOM in Node gives you a node tree and event dispatch but no layout and no painting, so every box measures zero and nothing can be on top of anything. The price is real: engines to install, a build step to compile the component for the browser, and slower runs per case than a Node-side render.

code

typescript · 12 lines
typescript
import { test, expect } from '@playwright/test';

test('the toast does not block the confirm button', async ({ mount, page }) => {
  const picker = await mount('design-system/date-picker--with-toast');

  await expect(picker).toBeVisible();
  const box = await picker.boundingBox();
  expect(box!.height).toBeGreaterThan(0);

  await page.getByRole('button', { name: 'Confirm' }).click();
  await expect(page.getByRole('status')).toHaveText('Date saved');
});

go deeper

for a junior

Remember the headline: a Playwright component test runs in a real browser engine, so the CSS and layout in the test are the real thing rather than an approximation of the DOM.

for a middle

Be able to explain what layout buys you: computed boxes, stacking, media queries and hit-testing, and therefore the clipping and click-interception bugs a Node-side render cannot represent at all.

for a senior

Show that you weigh the cost too. Browser installs, a bundling step and seconds per case are the price, so reach for the engine where geometry or engine differences are genuinely the risk.

for a principal

Own the tradeoff across the suite: which checks truly need pixels and an engine matrix, and how much CI time that budget consumes for the confidence it returns.

## What "real engine" actually means A Playwright component test mounts your component inside one of the browser engines Playwright ships — Chromium, Firefox or WebKit — and drives it with exactly the same `Locator` API a full browser test uses. Nothing on the rendering path is simulated. The stylesheet your design-system package imports is parsed and cascaded, the layout engine computes a box for every element, web fonts load and change text metrics, transitions and animations run on a real clock, and a click is dispatched at a coordinate **after a hit-test**, against whatever element actually occupies that point. A Node-side render is a different machine entirely. A simulated DOM — a JavaScript implementation of the DOM API running in Node, such as jsdom — gives you a tree of nodes, attributes and synthetic event dispatch, but it has **no layout engine and no painting**. Elements have no size, `getBoundingClientRect()` reports zeros, media queries do not resolve against a viewport that does not exist, and "on top of" is meaningless because there is no geometry for stacking to describe. ## The bug classes that need geometry - **Clipping** — an `overflow: hidden` ancestor cutting off the bottom of a date picker's calendar. Without layout there is no clip to observe. - **Overlap and stacking** — a sticky data-grid header sitting over the first row, or a toast landing on the calendar's confirm button. - **Hit-testing** — a transparent overlay swallowing a click. Playwright's actionability checks catch it; a synthetic event dispatched straight at the node never notices the overlay. - **Visibility** — `display: none` applied by a media query, `visibility: hidden`, or a zero-height element mid-animation. - **Focus and scrolling** — real focus order, `scrollIntoViewIfNeeded()`, sticky positioning, and scroll containers that move the element out from under the pointer. - **Engine differences** — a selector or a flex/grid edge case that lays out differently in WebKit than in Chromium. - **Native controls** — a real `<input type="date">` or `<select>`, with the keyboard and IME behaviour the engine gives them. ## Why the assertions mean more Because the geometry is real, Playwright's web-first assertions say what they appear to say. `locator.click()` waits until the target is visible, stable, enabled and actually receives pointer events, then fails with an interception message naming the element that got in the way. `expect(locator).toBeVisible()` consults computed style and a non-empty box. `toBeInViewport()`, `toHaveCSS()` and `locator.boundingBox()` all have something real to measure. | Question about a design-system component | Simulated DOM in Node | Playwright component mount | |---|---|---| | Does it render the right text? | Yes | Yes | | Does the accessible name match? | Approximated from the tree | Read from the rendered accessibility tree | | Is the calendar clipped by its container? | No — no layout | Yes | | Does a toast intercept the confirm click? | No — no hit-testing | Yes, the click fails | | Does the WebKit build wrap the header differently? | No engine to differ | Yes, per project | ## What it costs 1. **Engines on disk.** The browsers must be installed (`npx playwright install`) on every machine and in CI, which is real bytes and real minutes. 2. **A build step.** The component source has to be compiled and served to the browser before it can be mounted, so there is a bundler between your source and your assertion — and a class of failure that belongs to the bundler, not the component. 3. **Slower cases.** Each test pays browser startup, page setup and cross-process communication. A mounted suite is measured in seconds per case where a Node-side render is measured in milliseconds. ## Where that leaves the choice The honest reading is narrow: the real engine buys **pixels, geometry and engine behaviour**, and nothing else. It does not make a component test more correct about props, callbacks or branching logic — those were already fine in Node and now simply run slower. So the checks that earn a mount are the ones whose oracle is visual or physical: does the popover escape its container, does the overlay block the button, does the grid header stay stuck, does the toast reach the viewport. Pushing every rendering check into a mounted run buys nothing but wall-clock time; leaving the geometric ones in a simulated DOM buys a green suite that proves nothing about what the user can actually click.

  • If the engine is real, why can a mounted component still behave differently from the same component in production?
    The engine is real but the page is not. The harness supplies no application shell, so global resets, theme tokens, web fonts, container widths and clipping ancestors are simply absent. The rendering path is honest; the surroundings are not.
  • Which Playwright assertions become meaningless without real layout?
    The geometric ones. `toBeVisible()` consults computed style and box size, `toBeInViewport()` and `boundingBox()` need a viewport and real boxes, and the actionability checks before `click()` need hit-testing. In a Node-side render each is either absent or a guess.

A simulated DOM is the wiring diagram of a room; a real engine is the room with the lights on and the furniture in it. Both tell you the switch is connected, but only one shows the sofa blocking the door.

saying these in an interview costs you the question

  • Claiming a simulated DOM applies enough CSS to catch clipping
  • Thinking the only difference between the two is test speed
  • Believing a real engine removes the need for Node-side unit tests
  • Saying visibility checks are equivalent because both read the DOM
  • Expecting a mounted case to cost the same as a Node-side render