Component Testing Configuration
What you'll learn​
- How Cypress uses a dev server and bundler to run component tests
- How to configure the recommended
devServerobject syntax - How Cypress detects and merges your Vite or Webpack configuration
- How to use a custom index file or spec pattern
- How to implement a fully custom dev server for unsupported bundlers
When you launch Cypress for the first time in a project, the app will
automatically guide you through setup and configuration. The Launchpad detects
your UI framework and bundler, lists the required dependencies needed for you to install, and scaffolds
a cypress.config file with a component.devServer block.
The sections below explain what that configuration does and how to customize it. For framework-specific options (such as Angular monorepos or Next.js), also see the overview guide for your UI library:
Dev Server and Bundler​
Component tests run inside a real browser, but unlike end-to-end tests they do not visit your production or staging app. Instead, Cypress starts a dev server that compiles and serves your component specs on demand.
The dev server is responsible for:
- Compiling each spec file (and your support file) with the same transforms your app uses in development (JSX/TSX, Vue SFCs, CSS modules, path aliases, and so on).
- Serving those compiled files over HTTP so Cypress App can load them.
- Shutting down cleanly when you close the Cypress App or finish a test run.
Cypress ships with built-in dev server implementations for Vite and
Webpack. You do not need to install @cypress/vite-dev-server or
@cypress/webpack-dev-server separately, they are bundled with the Cypress App.
How it works at runtime​
When you open or run component tests, Cypress:
- Reads
component.devServerfrom your Cypress config file. - Starts the matching dev server (Vite or Webpack) on an available port.
- Sets
baseUrltohttp://localhost:<port>. - Loads your component index HTML (by default
cypress/support/component-index.html) and dynamically imports your support file (if configured) and the active spec, then hands control to Cypress.
Recommended configuration​
The recommended way to configure component testing is to use the component.devServer object. Specify your UI framework and bundler and Cypress will wire up the correct dev server implementation for you:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react', // your UI framework
bundler: 'vite', // 'vite' or 'webpack'
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'react', // your UI framework
bundler: 'vite', // 'vite' or 'webpack'
},
},
})
This is the configuration that Cypress App scaffolds during project setup. It is all most projects need.
Supported framework and bundler values​
framework | Supported bundler values | Notes |
|---|---|---|
react | vite, webpack | |
vue | vite, webpack | |
svelte | vite, webpack | |
next | webpack | Uses Next.js-specific Webpack presets. See React overview — Next.js. |
angular | webpack | Supports an options.projectConfig override. See Angular overview. |
Community framework definitions (packages named cypress-ct-* or
@org/cypress-ct-*) can also be used as the framework value when paired with
a supported bundler. See Custom Frameworks.
Automatic bundler configuration detection​
When you use the component.devServer object, Cypress tries to reuse the same bundler
configuration you already use to develop your app. You usually do not need
to duplicate your entire Vite or Webpack config in your Cypress config file.
Vite​
If you omit viteConfig, Cypress searches upward from your project root for a vite.config.ts|js|mjs|cjs|mts|cts file.
When a config file is found, Cypress loads it and merges in Cypress-specific settings (plugins, public path, spec entries, and file-system allow rules).
If no config file is found, Cypress shows an error asking you to add a vite.config file or pass a viteConfig option explicitly.
Webpack​
If you omit webpackConfig, Cypress searches upward from your project root for a webpack.config.ts|js|mjs|cjs|mts|cts file.
For meta-frameworks like Next.js and Angular, Cypress applies framework-specific presets before merging your config. For React, Vue, and Svelte, Cypress merges your detected Webpack config with a Cypress-specific overlay.
If no Webpack config can be detected and no framework preset applies, Cypress
shows an error asking you to add a webpack.config file or pass a
webpackConfig option explicitly.
Overriding bundler configuration​
Pass viteConfig or webpackConfig when you need to customize what Cypress
uses. For example, to add plugins, tweak aliases, or point to a config file
outside the project root.
Both options accept either a config object or an async function that returns a config object.
Vite overrides​
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const customViteConfig = require('./vite.config.custom')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
// Use a specific Vite config object
viteConfig: customViteConfig,
// Or compute one at runtime
viteConfig: async () => {
const base = await import('./vite.config')
return {
...base.default,
// test-only overrides
}
},
},
},
})
import { defineConfig } from 'cypress'
import customViteConfig from './vite.config.custom'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
// Use a specific Vite config object
viteConfig: customViteConfig,
// Or compute one at runtime
viteConfig: async () => {
const base = await import('./vite.config')
return {
...base.default,
// test-only overrides
}
},
},
},
})
Webpack overrides​
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
const webpackConfig = require('./webpack.config')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
webpackConfig,
webpackConfig: async () => {
const base = await import('./webpack.config')
return {
...base.default,
// test-only overrides
}
},
},
},
})
import { defineConfig } from 'cypress'
import webpackConfig from './webpack.config'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
webpackConfig,
webpackConfig: async () => {
const base = await import('./webpack.config')
return {
...base.default,
// test-only overrides
}
},
},
},
})
Function syntax (advanced)​
If you need direct access to the dev server API — for example, to pass options that are not exposed on the object syntax — use the function syntax and import the dev server from the appropriate package:
import { defineConfig } from 'cypress'
import { devServer as viteDevServer } from '@cypress/vite-dev-server'
export default defineConfig({
component: {
devServer(cypressDevServerConfig) {
return viteDevServer({
...cypressDevServerConfig,
framework: 'react',
viteConfig: async () => {
const config = await import('./vite.config')
return config.default
},
})
},
},
})
import { defineConfig } from 'cypress'
import { devServer as webpackDevServer } from '@cypress/webpack-dev-server'
export default defineConfig({
component: {
devServer(cypressDevServerConfig) {
return webpackDevServer({
...cypressDevServerConfig,
framework: 'react',
webpackConfig: require('./webpack.config.js'),
})
},
},
})
The function receives a cypressDevServerConfig object with:
| Property | Description |
|---|---|
specs | Spec files Cypress is about to run |
cypressConfig | The resolved Cypress configuration |
devServerEvents | Event emitter for compile lifecycle events |
It must return (or resolve to) an object with:
| Property | Description |
|---|---|
port | Port the dev server is listening on |
close | Optional callback to shut the server down |
You can store additional dev server options on component.devServerConfig when
using the function syntax. This field is passed as the second argument to your
devServer function.
devServerPublicPathRoute​
The devServerPublicPathRoute option controls the URL path prefix Cypress uses
to load compiled specs and assets. It defaults to /__cypress/src.
In most projects the default works well. You may need to change it if component
tests reference assets from your app's public directory and those paths must
match your Vite base setting. For Vite 5+, set an empty string to align with
Vite's default public path:
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServerPublicPathRoute: '',
devServer: {
framework: 'react',
bundler: 'vite',
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServerPublicPathRoute: '',
devServer: {
framework: 'react',
bundler: 'vite',
},
},
})
Use caution when overriding this value — an incorrect public path can cause specs or assets to fail to load. See the configuration reference for details.
Custom Index File​
By default, Cypress renders your components into an HTML file located at
cypress/support/component-index.html.
The index file allows you to add in global assets, such as styles, fonts, and external scripts.
You can provide an alternative path to the file using the indexHtmlFile option
in the component config options:
{
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
indexHtmlFile: '/custom/path/to/component-index.html',
},
}
Fully Custom Dev Server​
If your project uses a bundler other than Vite or Webpack, or you need complete
control over compilation, pass a custom function to component.devServer. This
is how community integrations and preview-server setups work.
The function receives a single DevServerOptions argument and must return (or
resolve to) a ResolvedDevServerConfig describing how Cypress should connect to
and stop the server.
interface DevServerOptions {
specs: Cypress.Spec[]
cypressConfig: Cypress.PluginConfigOptions
devServerEvents: NodeJS.EventEmitter
}
interface ResolvedDevServerConfig {
port: number // port the dev server is listening on
close?: (done?: () => void) => void // called by Cypress to shut the server down
}
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
async devServer({ specs, cypressConfig, devServerEvents }) {
const { port, close } = await startDevServer(
specs,
cypressConfig,
devServerEvents
)
return {
port,
close,
}
},
},
})
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
async devServer({
specs,
cypressConfig,
devServerEvents,
}: DevServerOptions) {
const { port, close } = await startDevServer(
specs,
cypressConfig,
devServerEvents
)
return {
port,
close,
}
},
},
})
Any requests triggered during a test using the devServerPublicPathRoute as
defined in the cypressConfig will be forwarded to your server. Cypress will
trigger a request for [devServerPublicPathRoute]/index.html when a test is
started. Your server needs to reply with the html-file referenced in
cypressConfig.indexHtmlFile and inject a script to load the support files and
the actual test.
function createServer(cypressConfig, bundleDir, port = 1234) {
const app = express()
// read kickstart script - see below for an example
const clientScript = readFileSync(
path.join(__dirname, './client-script.js'),
'utf8'
)
app.get(
cypressConfig.devServerPublicPathRoute + '/index.html',
async (_req, res) => {
// read custom index.html file
const html = await fs.readFile(
path.join(cypressConfig.repoRoot, cypressConfig.indexHtmlFile),
{ encoding: 'utf8' }
)
// inject kickstart-script
const output = html.replace(
'</head>',
`<script type="module">${clientScript}</script></head>`
)
res.send(output)
}
)
// you need to establish some url-to-path-mapping, if your bundler outputs
// the full directory structure you can map this one to one
app.use(cypressConfig.devServerPublicPathRoute, express.static(bundleDir))
app.listen(port)
}
For a real-world example, you can refer to this loader used by the Vite Dev Server.
The client script must retrieve information on the currently active test from the Cypress instance of the parent frame and load the corresponding bundle. If a support file is defined, it should be injected at the top of your test bundle or loaded before the test script.
const CypressInstance = (window.Cypress = parent.Cypress)
const devServerPublicPathRoute = CypressInstance.config(
'devServerPublicPathRoute'
)
let importPromise = Promise.resolve()
// If you do not bundle your support file along with the tests,
// you need to add a separate import statement for the support file.
const supportFilePath = CypressInstance.config('supportFile')
if (supportFilePath) {
const relative = supportFilePath.replace(
CypressInstance.config('projectRoot'),
''
)
importPromise = importPromise.then(
() => import(`${devServerPublicPathRoute}${relative}`)
)
}
// load the spec - you can extend the load function to also load css
const { relative } = CypressInstance.spec
importPromise = importPromise.then(
() => import(`${devServerPublicPathRoute}/${relative}`)
)
// trigger loading the imports
CypressInstance.onSpecWindow(window, importPromise)
// then start the test process
CypressInstance.action('app:window:before:load', window)
For a more complete example you can check out the kickstart script used in the vite-devserver.
The devServerEvents event emitter is used to communicate compile lifecycle
events between your server and Cypress:
- Emit
dev-server:compile:successto notify Cypress that a build finished and tests can run. - Listen for
dev-server:specs:changedto be notified when Cypress updates the set of active spec files (e.g. when a new spec is added), so you can recompile the new entry points.
// signal to Cypress that compilation is done
devServerEvents.emit('dev-server:compile:success')
// recompile when the active spec list changes
devServerEvents.on('dev-server:specs:changed', ({ specs }) => {
recompile(specs)
})
Spec Pattern for Component Tests​
By default, Cypress looks for spec files anywhere in your project with an
extension of .cy.js, .cy.jsx, .cy.ts, or .cy.tsx. However, you can
change this behavior for component tests with a custom specPattern value. In
the following example, we've configured Cypress to look for spec files with
those same extensions, but only in the src folder or any of its
subdirectories.
{
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
},
}
Additional Config​
For more information on all the available configuration options, see the configuration reference.