skip to content

Why does a Cypress component mount cost more than a simulated-DOM render?

level: middleimportance: must knowfreq 68%

answer

  1. Two different execution environments, not two APIs
  2. A dev server and a browser both start
  3. Layout and paint happen on every mount
  4. The in-memory DOM has no box model
  5. Roughly seconds per test, not milliseconds

basics

~20 s

Cypress mounts the component in a real browser, so every test pays for a compiled dev-server bundle, a loaded page, real layout and paint, and a snapshot per command. That buys a real box model, real visibility and real CSS.

solid answer

~50 s

A Cypress component test runs in a real browser rather than an in-memory DOM. Every run compiles and serves your specs through a Vite or Webpack dev server, loads `cypress/support/component-index.html` in a browser, and then, per test, mounts the component, lets the engine lay it out and paint it, and records a Command Log snapshot for each command. The Cypress docs put that at roughly 1-2 seconds per test, well above a simulated-DOM render. What the seconds buy is evidence a simulated DOM cannot produce: an in-memory DOM has no box model, so it cannot tell you that a toast is covered by a sticky header, that a data grid's column is clipped, or that a click lands on the element actually on top. Cypress's `.should('be.visible')` and actionability checks read those facts from the browser.

code

javascript · 14 lines
javascript
import { StickyHeader } from './StickyHeader'
import { Toast } from './Toast'

describe('<Toast />', () => {
  it('is not covered by the sticky header', () => {
    cy.mount(
      <StickyHeader>
        <Toast tone="error" message="Rating not saved" />
      </StickyHeader>,
    )

    cy.contains('Rating not saved').should('be.visible')
  })
})

go deeper

for a junior

Be ready to say plainly that Cypress renders the component in a real browser rather than an in-memory DOM, and that this is slower per test.

for a middle

Explain where the seconds go — dev-server compile, page load, mount, layout, paint, snapshot — and name what a box model buys that an in-memory DOM cannot compute.

for a senior

Show you budget the cost per assertion, and that you can spot a suite paying browser prices for assertions that never touch layout.

for a principal

Own the run-time budget across the whole suite: at seconds per test, spec count becomes a capacity question for CI machines, not just a style preference.

A Cypress component test and a simulated-DOM render answer the same question — "does this component behave correctly?" — at radically different prices. Cypress mounts the component in a **real browser**, in real layout, with a real style and paint pipeline. A simulated DOM builds an in-memory object graph that implements the DOM interfaces without ever laying anything out. Understanding the cost means understanding what the extra money buys. ## Where the time actually goes A component test in Cypress is not just a function call. Each run has to: 1. Start a dev server (Vite or Webpack) that compiles your specs, your support file and everything they import with the same transforms your app uses. 2. Launch a real browser and load `cypress/support/component-index.html`, the page the component is mounted into. 3. Import the support file and the active spec into that page, then hand control to the Cypress driver. 4. For every test, mount the component, let the browser lay it out, paint it, and record a Command Log snapshot of each command. 5. Tear the mounted tree down and reset state before the next test. The Cypress documentation puts a component test at roughly **1-2 seconds each**, against 3-15 seconds for an end-to-end test and under 100 milliseconds for a `cy.request()` call. A simulated-DOM render sits an order of magnitude below the component test: there is no browser process, no compile-and-serve step, no layout and no paint. ## What the extra seconds buy The cost is not waste. It buys evidence a simulated DOM structurally cannot produce: - **A real box model.** A simulated DOM has no box model, so nothing has a width, a height or a position. Every geometric fact about your data grid — column widths, a sticky header's overlap, a toast that escapes its container — is unavailable. - **Real visibility.** Cypress's `.should('be.visible')` and its actionability checks are built on what the browser reports, so a toast covered by an overlay, hidden behind a stacking context or clipped by an ancestor's `overflow` fails the assertion. In a simulated DOM the same node is present and "visible" by any reasonable definition the runner can compute. - **Real CSS.** Cascade, custom properties, media and container queries, transitions and the styles your design tokens actually resolve to are evaluated by the same engine that will run in production. - **Real event dispatch.** Clicks land on the element the browser says is on top, not on the node you selected. | | Cypress component mount | Simulated-DOM render | | --- | --- | --- | | Where it runs | real browser | Node process | | Rough cost per test | 1-2 seconds | milliseconds | | Box model and layout | yes | none | | Visibility and overlap checks | real | not computable | | CSS cascade and paint | real engine | not applied | | Debugging surface | DevTools, Command Log | terminal output | ## The trade in practice Because the price is per test, the honest way to reason about it is per assertion, not per component. A test that asserts a rating widget renders five stars and calls `onRate` with `4` gains nothing from a real browser — the same assertion in a simulated DOM is correct and roughly a hundred times cheaper. A test that asserts the widget's tooltip is not clipped by its card, or that a toast stays clickable above a sticky header, is only meaningful in a browser. There is a second, quieter cost worth naming: **wall-clock time is not the only price**. A component run needs a browser and a dev server in CI, so the machine image, the memory ceiling and the failure modes are heavier than a Node-only runner's. Cypress does amortise part of the cost — the dev server starts once per run, not once per spec — but the per-test browser work does not amortise at all. ## How to keep the bill honest - Reach for a component mount when the assertion is about **layout, visibility, overlap, or real styling**, and leave pure logic and prop-plumbing assertions to a cheaper runner. - Keep each spec's mounts small. Re-mounting a whole page-sized composition to assert one label pays browser prices for a Node-priced question. - Watch spec count, not just spec duration. At 1-2 seconds per test, a design system with four hundred component tests is a ten-minute run before any end-to-end test starts. - Do not treat the cost as a reason to write fewer assertions per mount. Once a component is mounted, additional assertions against that same mounted tree are cheap; it is the mount itself that is expensive.

  • Does the dev server start once per spec or once per run, and does that change the per-test cost?
    Cypress starts one dev server for the whole run and shuts it down at the end, so compilation cost is amortised across specs. The per-test cost is not amortised at all: every test still mounts, lays out and paints in the browser, and Cypress still records a Command Log snapshot per command.
  • Given the cost, how do you decide whether a rating widget's test belongs in a Cypress component mount?
    Judge it per assertion. Asserting that five stars render and that `onRate` fires with `4` is pure logic and gains nothing from a browser. Asserting that the widget's tooltip is not clipped by its card, or that it stays clickable under an overlay, is only meaningful where a real box model exists.

It is the difference between checking a floor plan and walking the room: the plan tells you the sofa exists, only the room tells you the door no longer opens.

saying these in an interview costs you the question

  • Says a browser mount is basically as fast as jsdom
  • Claims a simulated DOM can check element overlap
  • Thinks the cost is only the browser launch
  • Assumes visibility assertions work anywhere
  • Treats the real browser as pure overhead with no payoff