Test-Driven UI Development With Cypress Component Testing
Use Cypress Component Testing for a practical red-green-refactor loop: configure the browser testbed, mount components, assert user-visible behavior, and troubleshoot common setup issues.
Cypress Component Testing lets you mount an individual UI component in a real browser, interact with it, and assert what it renders or does. To use it for test-driven UI development, first write a spec for an observable user behavior, run it to see the expected failure, implement the smallest change that satisfies it, and rerun the spec. That red-green-refactor sequence is a useful development practice; Cypress provides the browser mounting, interaction, and assertion tools, but does not prescribe a TDD methodology.
This guide uses React with Vite for the complete runnable example. Cypress also documents component testing for Angular, Vue, and Svelte, with framework and bundler compatibility that can change over time. Check the current Cypress Component Testing setup and compatibility guide before configuring a project.
1. What component testing covers
Cypress Component Testing starts a development server, compiles component specs and support files, and serves them in a real browser. A spec mounts a component into Cypress’s testbed, queries its rendered DOM, performs browser interactions, and checks the result. This exercises browser-rendered UI rather than a simulated DOM.
The boundary is the component and its test setup. It does not visit the deployed production or staging application. Use component tests for focused behavior and rendering; use end-to-end coverage for full journeys that depend on routing, deployment configuration, or integrated services. The layers complement each other.
2. Set up Cypress Component Testing
Install Cypress
In an existing React and Vite project, install Cypress as a development dependency:
npm install --save-dev cypress
Open Cypress and follow its Component Testing setup flow. The Launchpad can detect a framework and bundler and scaffold configuration. For a CommonJS Cypress configuration, the relevant shape is:
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
})
Save this as cypress.config.js if that matches the project’s module setup. If the project uses a different module format, use the corresponding configuration syntax. Cypress can reuse a discoverable Vite or Webpack configuration; projects with custom aliases, plugins, or generated framework settings may need explicit viteConfig or webpackConfig.
Create the support file and mount command
For repeated React specs, define a reusable mount command in cypress/support/component.js:
import { mount } from 'cypress/react'
Cypress.Commands.add('mount', mount)
// Example TypeScript declaration, if the project uses TypeScript:
// declare global {
// namespace Cypress {
// interface Chainable {
// mount: typeof mount
// }
// }
// }
Ensure the component support file imports this command. Cypress’s generated setup may already create that import. The mount API uses framework-specific adapters; for application-wide providers, wrap the mounted component in a custom mount helper so each spec does not repeat provider setup.
3. Write the first test before the behavior
Suppose a counter must show its initial value and increase when the user clicks a button. Start with the user-visible expectation. Create src/Counter.cy.jsx:
import React from 'react'
import Counter from './Counter'
describe('Counter', () => {
it('increments the displayed count when the user clicks Increment', () => {
cy.mount(<Counter initialCount={2} />)
cy.findByRole('button', { name: 'Increment' }).click()
cy.findByText('Count: 3').should('be.visible')
})
})
This example uses Cypress Testing Library queries such as findByRole and findByText. Add the appropriate Testing Library Cypress package and support import if it is not already in the project. Alternatively, use Cypress’s built-in DOM queries with a stable selector, such as cy.get('[data-cy="increment"]').click(), and assert the rendered output. Prefer role and accessible name when they express how a user identifies the control; use a dedicated test attribute when no stable user-facing query fits.
Run the component spec with the project’s Cypress Component Testing command, for example:
npx cypress open --component
The spec should fail if Counter does not yet exist or lacks the expected behavior. This is the red step: verify that the failure points to the missing behavior, rather than a broken test setup.
Implement the smallest behavior
Now create src/Counter.jsx:
import React, { useState } from 'react'
export default function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount)
return (
<section>
<p>Count: {count}</p>
<button type="button" onClick={() => setCount((value) => value + 1)}>
Increment
</button>
</section>
)
}
Rerun the spec. It should pass when the displayed count changes from 2 to 3 after the click. That is the green step. Refactor only while the behavior remains covered, then add tests for meaningful alternate inputs and boundaries.
4. Cover callbacks and alternate states
Visible output is usually the clearest contract. When a component’s contract includes notifying a parent, assert the callback too. Cypress spies make the event observable:
it('reports the next value to its parent', () => {
const onChange = cy.spy().as('onChange')
cy.mount(<CounterControl value={4} onChange={onChange} />)
cy.findByRole('button', { name: 'Increment' }).click()
cy.get('@onChange').should('have.been.calledWith', 5)
})
The corresponding component needs to call onChange with the new value. This example assumes the control receives a value prop; adapt it to the component’s actual contract. A spy is useful for event behavior, but retain assertions on rendered state where that is the user-facing requirement.
For each important behavior, consider cases that affect the visible result:
- Initial, alternate, and missing props, including defaults.
- Empty, loading, error, and populated states where the component owns those states.
- Lower and upper boundaries, disabled controls, and repeated interactions.
- Callback arguments and whether a callback should be omitted for invalid or disabled actions.
- Long labels or content that can change layout or accessible names.
Do not turn every implementation detail into a test. Keep the spec centered on behavior a user or a parent component can observe.
5. Framework setup and mounting patterns
| Framework | Typical mount shape | Setup detail |
|---|---|---|
| React | cy.mount(<Component prop="value" />) |
Wrap in shared providers when the application requires them. |
| Vue | cy.mount(Component, { props: { value: 'value' } }) |
Install shared Vue plugins in a reusable mount command; pass event spies through the relevant event prop. |
| Angular | Mount with component properties and framework-specific imports, declarations, or providers. | Standalone components have setup considerations distinct from other Angular components; follow the Angular adapter guidance. |
| Svelte | Use the Cypress Svelte mount adapter. | Check current support status and bundler compatibility; Cypress labels its Svelte component setup Alpha in the cited compatibility guidance. |
Official compatibility is version-specific. The current Cypress getting-started guide lists React 18–19 with Vite 8 or Webpack 5, Next.js 15–16 with React 18–19 and Webpack 5, Vue 3 with Vite 8 or Webpack 5, Angular 21–22 with Webpack 5, and Svelte 5 with Vite 8 or Webpack 5 as Alpha. Treat these as a snapshot from the research current on 2026-10-03 UTC and verify the live compatibility table before upgrading or starting a project.
Vue and Angular have framework-specific overview and adapter instructions. Cypress notes that Nuxt does not receive dedicated framework treatment: because Cypress does not execute nuxt.config, aliases or auto-imports used by a mounted component may need explicit handling. If a framework is not officially supported, Cypress exposes a framework-definition mechanism for community integrations; this is an extension route, not equivalent to first-party support.
6. A maintainable red-green-refactor workflow
- Describe the behavior. Name the spec in terms of what the user sees or does.
- Mount a meaningful starting state. Supply the props, providers, or dependencies needed for that scenario.
- Query and interact. Select a control by accessible role and name where possible, then use Cypress commands such as
.click()or typing commands. - Assert the outcome. Check visible text, state, accessibility-relevant attributes, or a documented callback.
- Run the spec and inspect the failure. Confirm it fails for the expected missing behavior.
- Make the smallest implementation change. Rerun the focused spec until it passes.
- Refactor with coverage in place. Add focused cases for important alternate props, empty states, and boundaries.
This is an editorially recommended way to apply TDD with Cypress. Cypress documents the mounting and browser-testing capabilities; it does not require this sequence.
7. Troubleshooting common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Cypress cannot start the component dev server. | The component framework or bundler is missing or incorrect in component.devServer, or a project config cannot be discovered. |
Set the correct framework and bundler. Confirm the app’s dev server works, then pass explicit Vite or Webpack configuration if discovery is insufficient. |
| A component import fails on an alias or plugin. | The component build does not receive the same alias or plugin configuration as the application build. | Provide the needed aliases/plugins in Cypress’s bundler config and keep the relevant application transforms available to the component test server. |
| A Nuxt component references an unknown auto-import or alias. | Cypress does not execute nuxt.config as a Nuxt application runtime. |
Configure the needed aliases or imports explicitly for the component test build, or isolate the component from Nuxt-specific runtime assumptions. |
cy.mount is undefined. |
The framework mount adapter was not registered, or the component support file did not load. | Import the adapter’s mount, register the custom command, and ensure Cypress loads the component support file. |
| The component renders but lacks provider-dependent data. | The test mounted it outside the application’s context providers or plugins. | Create a reusable custom mount that installs the required providers, then keep scenario-specific props explicit per test. |
| An assertion passes inconsistently or targets the wrong element. | The selector is unstable, duplicated, or based on implementation structure. | Use a unique role and accessible name where possible, or add a stable test attribute. Scope the query to the relevant component region. |
| A callback assertion never fires. | The spy was not passed to the correct prop/event, or the interaction did not reach an enabled control. | Check the component’s event contract, pass the spy through that prop, and assert the control is actionable before clicking. |
8. Runtime, reliability, and cost considerations
Component specs avoid launching a complete deployed application journey for every focused UI behavior, but actual run time depends on the component build, browser, spec count, and project setup. Keep component specs focused and share expensive application context through a mount helper. Avoid adding arbitrary delays when the test can wait for a visible state or a meaningful condition.
For reliable results, make each spec establish its own initial props and dependencies. Do not depend on a prior spec’s browser state. Prefer observable assertions over timing assumptions, and keep network or service dependencies outside a component test unless that integration is itself the behavior under test. Cypress’s component dev server uses the app’s development transforms, so a passing component spec does not prove that the deployed bundle, routes, or production service integrations work.
Cypress’s cited component-testing setup does not establish a price or performance benchmark, so this guide makes no numeric speed or cost claim. Account for the developer time to configure framework-specific transforms and shared context, and retain end-to-end tests for integrated journeys that component tests cannot cover.
9. Or skip the browser setup
If you need a screenshot of a rendered page while building or reviewing UI, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It does not replace Cypress component tests: Cypress checks behavior through assertions, while ScreenshotNeo returns an image or PDF capture.
One GET request captures a URL. See the ScreenshotNeo API documentation for the request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot and page-info tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
10. Frequently asked questions
Does Cypress Component Testing use a real browser?
Yes. Cypress mounts components in a real browser rather than a simulated DOM.
Is component testing a replacement for end-to-end testing?
No. It covers isolated component behavior and rendering. Use end-to-end tests for complete flows involving routing, deployment, and integrated services.
Does Cypress require test-driven development?
No. Cypress supplies mounting, interaction, and assertion primitives. The red-green-refactor sequence in this guide is a development practice you can choose to use.
Can I test components that need application-wide providers?
Yes. A custom mount command can wrap a component in the required React providers or install Vue plugins, while each spec supplies its own scenario-specific inputs.


