skip to content

Component Harness

Mounting one component in a real browser instead of loading the whole app: the mount command you register, its props and spies, its styling and dev server, and what a mount cannot prove.

on this pageshow

explore

questions

20

Cypress has no built-in cy.mount(): how do you make it available to every component spec?

level: juniorimportance: must knowfreq 82%

answer

  1. Some commands you install yourself
  2. Look in the component support file
  3. One adapter package per UI framework
  4. Registered once, not imported per spec
  5. The launchpad scaffolds it for you

basics

~10 s

Cypress ships no cy.mount command. You import mount from a framework adapter - cypress/react, cypress/vue, cypress/angular or cypress/svelte - and register it once with Cypress.Commands.add('mount', mount) in cypress/support/component.js, which loads before every spec.

solid answer

~40 s

Component testing is the one Cypress mode with a command you install yourself. Each framework renders differently, so Cypress publishes one adapter per framework - `cypress/react`, `cypress/vue`, `cypress/angular`, `cypress/svelte` - and each exports a `mount` function. In `cypress/support/component.js` you write `import { mount } from 'cypress/react'` and then `Cypress.Commands.add('mount', mount)`. That file is bundled and evaluated before every component spec, so `cy.mount` is global from then on: a spec for the design system's rating widget just calls `cy.mount(<RatingWidget />)` with no import of its own. Configuring component testing through the Cypress launchpad scaffolds that file with the registration already written. Registering rather than importing `mount` per spec is the documented recommendation, because it also gives you a single place to wrap every component in whatever context the library needs.

code

javascript · 12 lines
javascript
// cypress/support/component.js
import { mount } from 'cypress/svelte'

Cypress.Commands.add('mount', mount)

// cypress/component/Toast.cy.js
import Toast from '../../src/Toast.svelte'

it('renders the design system toast', () => {
  cy.mount(Toast)
  cy.get('[data-cy=toast]').should('be.visible')
})

go deeper

for a junior

Be ready to say plainly that cy.mount is not built in, name the adapter package for your framework, and point at cypress/support/component.js as the file that registers it.

for a middle

Explain why the support file is the right home: it is bundled and evaluated before every component spec, so one Cypress.Commands.add call makes cy.mount global with no per-spec import.

for a senior

Expect to be asked what your registered mount does beyond calling the adapter, and to justify keeping it thin enough that a spec still reveals what the component depends on.

for a principal

Own the standard for a whole library: one registration per Cypress project, what a two-framework monorepo therefore costs, and how you migrate every spec when an adapter's entry point changes.

## Why the runner cannot ship one mount Every Cypress command works against a document. `cy.get()` queries what the browser is showing, `.click()` dispatches a real event into it, `.should()` retries until the DOM agrees. Nothing in that model knows what a *component* is. Turning a `RatingWidget` module into DOM is framework work, and the four supported frameworks do it four different ways: React creates a root and renders into it, Angular boots Angular's `TestBed`, Vue goes through Vue Test Utils, Svelte calls Svelte's own `mount`. Rather than guess, Cypress ships one small adapter per framework and asks you to wire up the one your project uses. | Framework | Import path | Renders through | |---|---|---| | React | `cypress/react` | React's `createRoot` and `root.render` | | Vue | `cypress/vue` | Vue Test Utils' `mount` | | Angular | `cypress/angular` | Angular's `TestBed` | | Svelte | `cypress/svelte` | Svelte's own `mount` | All four export a function named `mount` with the same shape - the component first, an options object second - and all four return a Cypress chain. That sameness is why the registration is a one-liner. ## The registration For component testing, `supportFile` defaults to `cypress/support/component.js`. Cypress compiles and bundles that file and evaluates it before every component spec in the run, which makes it the natural home for anything all of them need: ```js // cypress/support/component.js import { mount } from 'cypress/react' Cypress.Commands.add('mount', mount) ``` `Cypress.Commands.add(name, fn)` attaches `name` to the `cy` object for the rest of the run. From that point a spec for the design system's rating widget is ordinary Cypress: ```jsx import RatingWidget from './RatingWidget' describe('<RatingWidget />', () => { it('reports the star the reviewer picked', () => { cy.mount(<RatingWidget />) cy.get('[data-cy=star-4]').click() cy.get('[data-cy=rating-value]').should('have.text', '4') }) }) ``` Notice what is **not** in that spec: no import of `mount`, no render helper, no teardown. Only the component and the same commands an end-to-end spec would use. ## Register once, or import per spec? Importing `mount` straight into a spec and calling it does work. Registering it is still the documented recommendation, for two reasons: - **No per-spec import.** `cy.mount` is global once the support file has run, so two hundred component specs carry no setup import between them. - **One place to grow.** The registered function is yours. Wrapping every component in the library's theme provider, or defaulting an option, becomes an edit in one file rather than two hundred. ## The TypeScript half `Cypress.Commands.add` is a runtime call, so TypeScript has to be told about the new command separately or `cy.mount` is a type error. The scaffolded TypeScript support file merges the declaration in place: ```ts import { mount } from 'cypress/react' declare global { namespace Cypress { interface Chainable { mount: typeof mount } } } Cypress.Commands.add('mount', mount) ``` Typing it as `typeof mount` rather than a hand-written signature keeps the adapter's own props and options checked, and keeps working when the adapter's signature changes. The same block can live in a `cypress.d.ts` at the project root, as long as your `tsconfig.json` includes it. ## What the launchpad writes for you Configuring component testing through the Cypress app detects the framework, fills in the `component` block of the config file, and scaffolds three things: 1. `cypress/support/component.js` - the registration above, plus an import of the shared commands file. 2. `cypress/support/component-index.html` - the page components are rendered into. 3. The `component` section of `cypress.config.js`, including `devServer`. Most teams therefore never type the registration; they meet it the first time they want to change it. Knowing that `cy.mount` is *just* a custom command is what makes that change unintimidating. ## When the wiring is wrong - **`cy.mount is not a function`** - the registration never ran. Usually it was put in the end-to-end support file, or `supportFile` is set to `false`. - **The adapter import path does not match the framework** - importing `mount` from `cypress/vue` in a React project fails at bundle time, not at assert time. - **As of Cypress 16, the four adapters above are the complete official set.** The separate `cypress/angular-zoneless` entry point was merged into `cypress/angular` in that release and is no longer shipped with the binary, so an Angular project upgraded from an older major must change that import to `cypress/angular`.

  • Can you skip the registration and import mount directly in a spec?
    Yes, and it runs. You lose the two things registration buys: `cy.mount` available in every spec with no import, and a single place to change how every component is mounted. The Cypress docs recommend registering for exactly that reason, and the launchpad scaffolds it that way.
  • What does the TypeScript registration need that the JavaScript one does not?
    A declaration merge. `Cypress.Commands.add` is a runtime call, so the `Chainable` interface has to learn about `mount` separately - `declare global { namespace Cypress { interface Chainable { mount: typeof mount } } }` - in the support file or in a `cypress.d.ts` that your `tsconfig.json` includes.

saying these in an interview costs you the question

  • Says cy.mount is a built-in Cypress command like cy.get
  • Imports mount into every spec instead of registering it once
  • Cannot name which package the framework adapter comes from
  • Puts the registration in the end-to-end support file instead
open as a page

In a Cypress component test, how do you assert that a component invoked its `onRate` callback prop?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Create a Cypress spy with cy.spy(), name it with .as(), and pass it into cy.mount() as the prop. Drive the component, then assert through the alias: cy.get('@onRate').should('have.been.calledWith', 4). The alias is what makes the assertion retry until the callback fires.

open as a page

Why does a component mounted by Cypress's cy.mount() render unstyled?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Cypress renders components into cypress/support/component-index.html, not your application's page, so nothing the app's entry file does at startup has run. Import your global stylesheets in cypress/support/component.js, or add the same link tags to that index file.

open as a page

In a Cypress component test, why does an aliased spy assertion retry when `expect(spy)` does not?

level: middleimportance: must knowfreq 62%

basics

~20 s

cy.get('@alias') is a Cypress query, so the chained assertion is re-run against the spy until it passes or defaultCommandTimeout expires. A bare expect(spy) inside .then() is plain Chai: it runs once, at that instant, and loses the race whenever the callback is late.

open as a page

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

level: middleimportance: must knowfreq 68%

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.

open as a page

What does Cypress's component.devServer framework and bundler pair actually do?

level: middleimportance: must knowfreq 66%

basics

~20 s

The framework and bundler pair tells Cypress which dev server to start and how to compile your specs. Cypress boots Vite or webpack on a free port, reuses your project's own bundler config file, and serves the compiled support file and spec.

open as a page

Your data grid passes every Cypress component test but breaks in the app. Why?

level: seniorimportance: must knowfreq 58%

basics

~20 s

The mount renders into an almost-empty index page, not the real one. Missing ancestors, siblings, real data and the 500x500 default component viewport mean the grid is correct in an environment your application never builds.

open as a page

Which frameworks and bundlers does Cypress 16 support for component tests?

level: juniorimportance: should knowfreq 52%

basics

~10 s

Cypress 16 ships official mount libraries for React 18-19, Vue 3, Angular 21-22 and Svelte 5, served by Vite 8 or Webpack 5. Angular and Next.js 15-16 are Webpack-only, and Svelte is still alpha.

open as a page

What is Cypress's component-index.html, and what must it contain for cy.mount to work?

level: middleimportance: should knowfreq 52%

basics

~20 s

It is the HTML page Cypress renders components into, at cypress/support/component-index.html by default. It must contain an element carrying the data-cy-root attribute; the mount adapters query for it and throw if it is missing. The indexHtmlFile option moves the page.

open as a page

In Cypress component tests, how do you pass slot or child content to `cy.mount()`?

level: middleimportance: should knowfreq 46%

basics

~20 s

Through a different channel per adapter. In React the children are part of the JSX you hand to cy.mount(). In Vue you fill Vue Test Utils' slots option, keyed by slot name. In Angular you mount a template string with the content inline.

open as a page

Between two Cypress component tests, what is torn down automatically and what survives?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Before each component test Cypress unmounts the rendered component and clears cookies, localStorage and sessionStorage in all domains. Everything else survives - IndexedDB, module-level state in your own bundle, listeners on document, and real timers. Component tests cannot configure test isolation.

open as a page

Why does a second `cy.mount()` in one Cypress test reset the component's state?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Because it is a fresh mount, not an update. As of Cypress 16 the cypress/react adapter unmounts the existing root and builds a new one, deliberately wiping state. To change props on the same instance, use the rerender function cy.mount() yields.

open as a page

Adding viteConfig to Cypress's component.devServer broke your aliases. Why?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Passing viteConfig or webpackConfig replaces Cypress's automatic detection of your project's bundler config. Cypress then compiles specs with only what you passed, so plugins and aliases declared in the real config file disappear. Import that file and spread it instead.

open as a page

In Cypress, how much should the single registered cy.mount for a design system wrap?

level: principalimportance: should knowfreq 36%

basics

~20 s

Wrap only what every component genuinely needs to render at all, and let the rest be opted into per spec. A fat shared mount removes boilerplate but hides each component's real dependencies and lets components pass against context production never supplies.

open as a page

How far should Cypress's component support matrix drive a design system's upgrades?

level: principalimportance: should knowfreq 36%

basics

~20 s

Treat the matrix as a normal dependency constraint and keep the framework inside its window by default. Allow pinning the runner only as a dated pause with a named owner, never as an unexamined policy.

open as a page

How much of your real build should a Cypress component harness reuse?

level: principalimportance: should knowfreq 36%

basics

~20 s

Reuse the application's real bundler config by default. Every difference between the harness build and the real build is a class of defect the component suite cannot see, so keep divergence to a short, named list you can justify and re-check elsewhere.

open as a page

Why do cy.visit(), cy.session() and cy.origin() throw inside a Cypress component spec?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Each would destroy or bypass the harness page the component was mounted into. When Cypress runs in component mode the mount adapter overwrites all three commands to throw, with messages such as 'cy.visit from a component spec is not allowed'.

open as a page

In a Cypress component test of a Next.js page, why are its props undefined?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A Cypress mount only constructs and renders the page component. Next.js data functions such as getServerSideProps and getStaticProps run on the server, and nothing in a component test calls them, so their props never arrive.

open as a page

In a Cypress component test, how do you fast-forward a toast's auto-dismiss timer?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Call cy.clock() before cy.mount(), so the fake timers are installed before the component can schedule anything, then move time with cy.tick(5000) and assert the toast is gone. Cypress restores the real clock automatically between tests.

open as a page