Cypress component testing uses the same Cypress Test Runner you already know from E2E testing — the same command chain, the same assertions, the same time-travel debugging. The difference is the setup: instead of visiting a URL, you mount() a single component. Cypress needs to know which framework (React, Vue, Angular) and bundler (Vite, Webpack) your project uses so it can compile and render your component correctly.
Configuring Component Testing — Framework and Bundler Setup
The setup process detects your framework and bundler, adds the necessary dependencies, and generates the configuration.
// ── STEP 1: Open Cypress and select Component Testing ──
// $ npx cypress open
// → Select "Component Testing" (not E2E Testing)
// → Cypress detects your framework (React, Vue, Angular)
// → Cypress detects your bundler (Vite, Webpack, Next.js, Create React App)
// → Click "Continue" — Cypress generates the configuration
// ── STEP 2: cypress.config.ts — component testing section ──
import { defineConfig } from 'cypress';
export default defineConfig({
// E2E testing config (existing)
e2e: {
baseUrl: 'https://www.saucedemo.com',
specPattern: 'cypress/e2e/**/*.cy.{js,ts}',
},
// Component testing config (NEW)
component: {
devServer: {
framework: 'react', // 'react', 'vue', 'angular', 'svelte'
bundler: 'vite', // 'vite', 'webpack'
// For Next.js: framework: 'next'
// For Create React App: bundler: 'webpack'
},
specPattern: 'src/**/*.cy.{js,ts,jsx,tsx}', // CT specs live next to components
supportFile: 'cypress/support/component.ts',
},
});
// ── STEP 3: cypress/support/component.ts — CT support file ──
/*
import { mount } from 'cypress/react18'; // React 18
// import { mount } from 'cypress/react'; // React 17
// import { mount } from 'cypress/vue'; // Vue 3
// import { mount } from 'cypress/angular'; // Angular
// Add the mount command globally
Cypress.Commands.add('mount', mount);
// Optional: import global CSS so components render with your app styles
import '../../src/index.css';
import '../../src/App.css';
// TypeScript declaration
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount;
}
}
}
*/
// ── STEP 4: Run component tests ──
// Interactive: npx cypress open --component
// Headless CI: npx cypress run --component
// ── Project structure with CT specs alongside components ──
/*
src/
components/
ProductCard/
ProductCard.tsx ← The component
ProductCard.cy.tsx ← Component test (RIGHT NEXT TO IT)
ProductCard.module.css ← Component styles
LoginForm/
LoginForm.tsx
LoginForm.cy.tsx ← Component test
Modal/
Modal.tsx
Modal.cy.tsx ← Component test
cypress/
e2e/ ← E2E test specs (separate)
checkout.cy.ts
login.cy.ts
support/
component.ts ← CT support (mount command, global CSS)
e2e.ts ← E2E support (custom commands)
*/
// ── Framework-specific setup differences ──
const FRAMEWORK_SETUP = {
'React + Vite': {
devServer: "{ framework: 'react', bundler: 'vite' }",
mount_import: "import { mount } from 'cypress/react18'",
spec_extension: '.cy.tsx',
notes: 'Fastest setup — Vite HMR provides instant recompilation',
},
'React + Webpack (CRA)': {
devServer: "{ framework: 'create-react-app', bundler: 'webpack' }",
mount_import: "import { mount } from 'cypress/react18'",
spec_extension: '.cy.tsx',
notes: 'Use create-react-app framework preset for automatic config',
},
'Vue 3 + Vite': {
devServer: "{ framework: 'vue', bundler: 'vite' }",
mount_import: "import { mount } from 'cypress/vue'",
spec_extension: '.cy.ts',
notes: 'Mount accepts component + options (props, slots, plugins)',
},
'Angular': {
devServer: "{ framework: 'angular', bundler: 'webpack' }",
mount_import: "import { mount } from 'cypress/angular'",
spec_extension: '.cy.ts',
notes: 'Mount accepts component class + TestBed-like configuration',
},
'Next.js': {
devServer: "{ framework: 'next', bundler: 'webpack' }",
mount_import: "import { mount } from 'cypress/react18'",
spec_extension: '.cy.tsx',
notes: 'Supports Next.js App Router and Pages Router components',
},
};
Object.entries(FRAMEWORK_SETUP).forEach(([fw, info]) => {
console.log(`\n${fw}:`);
console.log(` devServer: ${info.devServer}`);
console.log(` mount: ${info.mount_import}`);
console.log(` Spec ext: ${info.spec_extension}`);
console.log(` Notes: ${info.notes}`);
});
ProductCard.cy.tsx sits in the same folder as ProductCard.tsx. This is different from E2E tests, which live in cypress/e2e/. The co-location makes it obvious which components have tests and which do not. When a developer modifies ProductCard.tsx, the test file is right there — impossible to forget. This convention is enforced by the specPattern: 'src/**/*.cy.{js,ts,jsx,tsx}' config.cypress/support/component.ts so that mounted components render with the same styles they have in the full application. Without these imports, components mount with no styling — bare HTML with default browser styles. Adding import '../../src/index.css' and any Tailwind/Bootstrap CSS ensures visual consistency between CT and the real app.devServer configuration must match your actual project setup. If your project uses Vite but you configure bundler: 'webpack', component mounting will fail with cryptic compilation errors. Cypress’s auto-detection (via npx cypress open --component) usually gets this right, but verify manually if you use a custom build configuration or monorepo structure.Common Mistakes
Mistake 1 — Putting component tests in cypress/e2e/ instead of next to the component
❌ Wrong: cypress/e2e/ProductCard.cy.tsx — separated from the component it tests, easy to forget and hard to find.
✅ Correct: src/components/ProductCard/ProductCard.cy.tsx — co-located, discoverable, and always visible when the component is modified.
Mistake 2 — Not importing global styles in the CT support file
❌ Wrong: Components mount with no CSS, making visual assertions meaningless and rendering unrecognisable.
✅ Correct: Adding import '../../src/index.css' to cypress/support/component.ts so all components render with the application’s real styles.