# Cypress Documentation > Cypress is a modern end-to-end testing framework for web applications, designed for developers to write, run, and debug tests easily. This file is the complete Cypress documentation corpus: all 325 pages listed in https://docs.cypress.io/llms.txt, concatenated in the same order. Each page begins with a `Source:` line giving the URL of its markdown; drop the `.md` for the HTML page. Split on those lines to separate the pages: the `---` rule before each one is only a visual break, and a page body may contain one. To fetch a single page instead of all of them, read the index at https://docs.cypress.io/llms.txt. --- Source: https://docs.cypress.io/app/ai/overview.md Section: Cypress App # AI in Cypress AI shows up in Cypress in two distinct ways, and knowing which one you need is the fastest route to the right page. **AI that writes tests for you.** You are still the author. Cypress Studio and [`cy.prompt`](/llm/markdown/api/commands/prompt.md) turn recorded interactions or plain language into real Cypress commands, so less of your time goes to boilerplate and selector archaeology. **Cypress made legible to your agents.** Here your AI coding tool is the consumer. Skills, `cypress tap`, Cloud MCP, and agent-ready reports give an agent accurate Cypress context, so it produces tests that follow your conventions instead of generic ones that break in CI. Most teams end up using both. The rest of this page is a map of what exists in each half. ## Write tests with AI Both options below produce ordinary Cypress code that you can read, edit, and commit. Neither is a black box: `cy.prompt` exposes the generated commands in the Command Log, and Studio writes directly into your spec file. * **[Cypress Studio](/llm/markdown/app/guides/cypress-studio.md)** records real interactions in your app and turns them into end-to-end tests. Connected to Cypress Cloud, Studio AI also recommends assertions as you record. Reach for it when you want to author a test by clicking through your app. * **[Generate tests with `cy.prompt`](/llm/markdown/app/guides/ai-test-generation.md)** turns natural language steps into executable Cypress commands, and can keep them working as your UI changes. Reach for it when describing a flow is faster than recording one. ## Give agents Cypress context An AI coding agent that knows nothing about Cypress writes tests that look plausible and then fail in CI: brittle selectors, arbitrary waits, and assertions that do not retry. These capabilities give it better information to work from. * **[Cypress skills for AI agents](/llm/markdown/app/tooling/ai-skills.md)** teach your coding agent to write, review, and explain tests the way an experienced Cypress engineer would. Install them once per project. * **[`cypress tap`](/llm/markdown/app/tooling/cypress-tap.md)** attaches your agent to a running open mode session from the terminal, so it can run a spec and then read the Command Log, the error, and the DOM as the Cypress app shows them. That closes the loop: the agent can verify its own fix instead of reporting pass or fail. The [`cypress-tap` skill](/llm/markdown/app/tooling/ai-skills.md#cypress-tap) packages this workflow so your agent drives it correctly. * **[Cloud MCP](/llm/markdown/cloud/integrations/cloud-mcp.md)** connects your agent to Cypress Cloud so it can read real run results, failures, and [Test Replay](/llm/markdown/cloud/features/test-replay.md) data while you work, rather than guessing at why something failed. * **[Cloud CLI](/llm/markdown/cloud/integrations/cloud-cli.md)** is the scriptable counterpart to Cloud MCP: the same Cloud run data as JSON in the terminal, for agents that work through a shell and for your own automation. The [`cypress-cloud-cli` skill](/llm/markdown/app/tooling/ai-skills.md#cypress-cloud-cli) instructs your agent on how to work through that recorded run and test data to find a failure's root cause. ## Analyze and debug with AI * **[Cypress AI](/llm/markdown/cloud/features/cypress-ai-features.md)** explains failures and summarizes test intent across recorded runs in Cypress Cloud. * **[Work with AI agents in Cypress Accessibility](/llm/markdown/accessibility/work-with-ai-agents.md)** and **[Work with AI agents in UI Coverage](/llm/markdown/ui-coverage/work-with-ai-agents.md)** cover tuning those reports so an agent can act on them, then prompting it to triage findings and plan the work. ## What each capability needs **No AI capability in Cypress requires a paid subscription.** Everything below works on the free [Starter plan](https://www.cypress.io/pricing?utm_source=docs.cypress.io&utm_medium=ai-overview), and some of it never touches Cypress Cloud at all. The exceptions are the agent-ready reports at the bottom of the table, which come with the Cypress Accessibility and UI Coverage solutions rather than with a Cloud plan. Paying raises limits rather than unlocking capabilities: a paid plan gets you a larger `cy.prompt` execution allowance and higher hourly limits on `cy.prompt` and Studio AI. See [AI usage limits](/llm/markdown/cloud/features/cypress-ai-features.md#Usage-limits) for the numbers. | Capability | Where it runs | Cypress Cloud account | Paid plan | | --- | --- | --- | --- | | Cypress Studio recording | Cypress App | Not needed | No | | Studio AI assertion suggestions | Cypress App | Free plan works | No | | `cy.prompt` | Cypress App | Free plan works | No | | Cypress skills for AI agents | Your editor | Not needed | No | | `cypress tap` | Your terminal | Not needed | No | | Cloud MCP | Your editor | Free plan works | No | | Cloud CLI | Your terminal | Free plan works | No | | Cypress AI failure and intent summaries | Cypress Cloud | Free plan works | No | | Agent-ready accessibility reports | Cypress Cloud | Yes | Included with Cypress Accessibility | | Agent-ready coverage reports | Cypress Cloud | Yes | Included with UI Coverage | The capabilities that need a Cloud account need one because Cypress Cloud is what routes their AI requests securely, applies your organization's controls, and tracks usage. See [Why do I need a Cloud account?](/llm/markdown/cloud/features/cypress-ai-features.md#Why-do-I-need-a-Cloud-Account) Organization admins and owners can turn AI capabilities on or off for everyone in the organization. See [Disabling AI features](/llm/markdown/cloud/features/cypress-ai-features.md#Disabling-AI-features). ## See also * [`cy.prompt()`](/llm/markdown/api/commands/prompt.md) - the command reference * [Cypress Studio](/llm/markdown/app/guides/cypress-studio.md) * [Cypress skills for AI agents](/llm/markdown/app/tooling/ai-skills.md) * [`cypress tap`](/llm/markdown/app/tooling/cypress-tap.md) * [Cloud MCP](/llm/markdown/cloud/integrations/cloud-mcp.md) * [Cloud CLI](/llm/markdown/cloud/integrations/cloud-cli.md) * [Cypress AI](/llm/markdown/cloud/features/cypress-ai-features.md) - availability, usage limits, and pricing for every AI capability --- Source: https://docs.cypress.io/app/component-testing/angular/api.md Section: Cypress App # Angular API The `cypress/angular` module provides the following methods and interfaces for mounting and testing Angular components. ## mount ``` import { mount } from 'cypress/angular' ```
DescriptionMounts an Angular component inside Cypress browser
Signature

mount<T>(component: Type<T> | string, config?: MountConfig<T>): Cypress.Chainable<MountResponse<T>>

ReturnsCypress.Chainable<MountResponse>
mount Parameters
NameTypeDescription
componentType<T> | stringAngular component being mounted or its template
configMountConfig<T> (optional)
### Example ``` import { mount } from '@cypress/angular' import { StepperComponent } from './stepper.component' import { MyService } from 'services/my.service' import { SharedModule } from 'shared/shared.module' it('mounts', () => { mount(StepperComponent, { providers: [MyService], imports: [SharedModule], }) cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') }) // or it('mounts with template', () => { mount('', { declarations: [StepperComponent], }) }) ``` ## createOutputSpy ``` import { createOutputSpy } from 'cypress/angular' ```
DescriptionCreates a new Event Emitter and then spies on it's emit method
Signature(alias: string) => any
ReturnsEventEmitter<T>
createOutputSpy parameters
NameTypeDescription
aliasstringalias name you want to use for your cy.spy() alias
### Example ``` import { StepperComponent } from './stepper.component' import { mount, createOutputSpy } from '@cypress/angular' it('Has spy', () => { mount(StepperComponent, { change: createOutputSpy('changeSpy') }) cy.get('[data-cy=increment]').click() cy.get('@changeSpy').should('have.been.called') }) ``` ## Interfaces ### MountConfig Additional module configurations needed while mounting the component, like providers, declarations, imports and even component @Inputs()
members
NameTypeDescription
componentProperties

Partial<{[P in keyof T]: T[P] extends InputSignal<infer V> ? InputSignal<V> | WritableSignal<V> | V : T[P]}> (optional)

signal based inference types need only apply if you are using signals within your component tests

### MountResponse Type that the `mount` function yields
members
NameTypeDescription
fixtureComponentFixture<T>Fixture for debugging and testing a component.
componentTThe instance of the root component class
See [https://angular.io/api/core/testing/ComponentFixture#componentInstance](https://angular.io/api/core/testing/ComponentFixture#componentInstance) --- Source: https://docs.cypress.io/app/component-testing/angular/examples.md Section: Cypress App # Angular Examples To mount a component with `cy.mount()`, import the component and pass it to the method: ``` import { StepperComponent } from './stepper.component' it('mounts', () => { cy.mount(StepperComponent) }) ``` ## Passing Data to a Component You can pass inputs and outputs to a component by setting [componentProperties](/llm/markdown/app/component-testing/angular/api.md#MountConfig) in the options: ``` cy.mount(StepperComponent, { componentProperties: { count: 100, }, }) ``` ## Testing Event Handlers Pass a Cypress [spy](/llm/markdown/app/guides/stubs-spies-and-clocks.md#Spies) to an event prop and validate it was called: ``` it('clicking + fires a change event with the incremented value', () => { cy.mount(StepperComponent, { componentProperties: { change: createOutputSpy('changeSpy'), }, }) cy.get('[data-cy=increment]').click() cy.get('@changeSpy').should('have.been.calledWith', 1) }) ``` ## Imports/Declarations/Providers If you need to set up any additional `imports`, `declarations`, or `providers` for your component to mount successfully, you can set them in the options (similar to setting them up in `ngModule` in a app): Note: `imports`, `declarations`, and `providers` options do not work with `standalone` components as they are set within the test `ngModule`. This is the default behavior in Angular 19. ``` cy.mount(ComponentThatFetchesData, { imports: [HttpClientModule], declarations: [ButtonComponent], providers: [DataService], }) ``` See [Default Declarations, Providers, or Imports](/llm/markdown/app/component-testing/angular/examples.md#Default-Declarations-Providers-or-Imports) to set up common options in a custom `cy.mount()` command to avoid having to repeat this boilerplate for each test. ## Using Standalone Not only are [Standalone Components](https://angular.io/guide/standalone-components) supported, they are the simplest components to write tests for. Standalone Components provide the Angular compiler with everything it needs to compile through its [`@Component()`](https://angular.io/api/core/Component) decorator. This means that in most cases a Standalone Component can be mounted without ever providing any `imports`, `decorators`, or `providers`. Mounting then becomes as simple as: ``` cy.mount(MyStandaloneComponent) ``` ## Using Angular Template Syntax The `cy.mount()` method also supports the Angular template syntax when mounting a component. Some developers might prefer this approach to the object based mount style: ``` cy.mount(``, { declarations: [StepperComponent], }) ``` > When using template syntax, the component needs to added to the declarations in the options parameter. Using with event emitter spy: ``` cy.mount('Click me', { declarations: [ButtonComponent] componentProperties: { onClick: createOutputSpy('onClickSpy'), }, }) cy.get('button').click(); cy.get('@onClickSpy').should('have.been.called'); ``` ## Accessing the Component Instance There might be times when you might want to access the component instance directly in your tests. To do so, use `.then()`, which enables us to work with the subject that was yielded from the `cy.mount()` command. In this case, mount yields an object that contains the rendered component and the fixture. In the below example, we use the component to spy directly on the `change` event emitter. ``` it('clicking + fires a change event with the incremented value', () => { cy.mount( '', { componentProperties: { change: new EventEmitter() }, declarations: [StepperComponent], } ).then((wrapper) => { console.log({ wrapper }) cy.spy(wrapper.component.change, 'emit').as('changeSpy') return cy.wrap(wrapper).as('angular') }) cy.get(incrementSelector).click() cy.get('@changeSpy').should('have.been.calledWith', 101) }) ``` ## Working with Legacy @Input() Decorators With the release of Angular 18, [signals](https://angular.dev/guide/signals) became the preferred way to handle data binding. However, legacy components that use `@Input()` and `@Output()` decorators are still supported. Interacting with legacy `@Input()` decorators is a bit different than working with signals. In order to update the `@Input()` value, you need to use the `componentRef.setInput` method so Angular change detection runs properly. Otherwise, you may see errors. ``` cy.mount(StepperComponent, { componentProperties: { count: 100 } }) .then(({ fixture }) => { return cy.contains('span', '100').wrap(fixture) }) .then((fixture) => { fixture.componentRef.setInput('count', 110) return cy.contains('span', '110') }) ``` ## Using createOutputSpy() To make spying on event emitters easier, there is a utility function called `createOutputSpy()` which can be used to automatically create an `EventEmitter` and setup the spy on it's `.emit()` method. It can be used like the following: ``` import { createOutputSpy } from 'cypress/angular' it('clicking + fires a change event with the incremented value', () => { // Arrange cy.mount('', { declarations: [StepperComponent], componentProperties: { change: createOutputSpy('changeSpy'), }, }) cy.get(incrementSelector).click() cy.get('@changeSpy').should('have.been.called') }) ``` ## Signals With Cypress 14, signal support is directly included in the `cypress/angular` testing harness. For the below examples, we'll be working with a very simple component called `TestComponent`, which looks something like shown below: ``` // app/components/test-component.component.ts import { Component, input, model } from '@angular/core' @Component({ selector: 'test-component', templateUrl: './test-component.component.html', standalone: true, }) export class TestComponent { title = input.required() count = model(1) } ``` ```

{{ title() }}

{{ count() }}

``` ### Testing Signals There are two ways to test signals within Cypress Component Testing: 1. [Inferred Generic Type](#Inferred-Generic-Type) 2. [Writable Signal](#Writable-Signal) #### Inferred Generic Type In the example below, the `title` prop being passed into our `TestComponent` is a `string`. A `string` is the generic type of our `input()` signal we defined in our `TestComponent`. ``` let titleProp = 'Test Component' cy.mount(TestComponent, { componentProperties: { title: titleProp, }, }) cy.get('[data-cy="test-component-title-display"]').should( 'have.text', 'Test Component' ) ``` Under the hood, Cypress wraps the generic value in a writable `signal()` and merges it into the prop. In other words, this example is really: ``` cy.mount(TestComponent, { componentProperties: { title: signal('Test Component'), }, }) ``` This works for any signal. Shown below is an example of testing a `model()` signal with a generic type `number` as seen in our `TestComponent`: ``` cy.mount(TestComponent, { componentProperties: { title: 'Test Component', count: 3, }, }) cy.get('[data-cy="test-component-count-display"]').should('have.text', '3') ``` #### Writable Signal Inferred generic types work very well for most test cases. However, they don't allow us to update the prop in the component after the prop is passed in. For this use case, we need to use a writable `signal()`. This allows us to test our one-way data binding for our `input()` signals. ``` const myTitlePropAsSignal = signal('Test Component') cy.mount(TestComponent, { componentProperties: { title: myTitlePropAsSignal, }, }) cy.get('[data-cy="test-component-title-display"]').should( 'have.text', 'Test Component' ) cy.then(() => { // now set the input() through a signal to update the one-way binding myTitlePropAsSignal.set('FooBar') }) cy.get('[data-cy="test-component-title-display"]').should('have.text', 'FooBar') ``` And our two-way data binding for our `model()` signals. ``` let count = signal(5) cy.mount(TestComponent, { componentProperties: { title: 'Test Component', count, }, }) cy.then(() => { // now set the model() through a signal to update the binding in the component count.set(8) }) cy.get('[data-cy="test-component-count-display"]').should('have.text', '8') // some action occurs that changes the count to 9 inside the component, which updates the binding in our test cy.get('[data-cy="test-component-count-incr"]').click() cy.get('[data-cy="test-component-count-display"]').should('have.text', '9') cy.then(() => { expect(count()).to.equal(9) }) ``` #### Change Spies Cypress doesn't propagate changes via spy from `input()` signals. For writable signals, such as `model()`s or `signal()`s, Cypress **will** propagate changes if an output spy is created with the prop's name suffixed with `Change`. In the example below, `countChange` will spy on changes to the `count` signal. ``` cy.mount(TestComponent, { componentProperties: { title: 'Test Component', count: 4, // @ts-expect-error countChange: createOutputSpy('countChange'), }, }) // some action occurs that changes the count cy.get('[data-cy="test-component-count-incr"]').click() cy.get('@countChange').should('have.been.called') ``` ## Testing Error States `cy.mount()` is an asynchronous Cypress command: it _enqueues_ the mount and returns immediately, before the component ever renders. When a component throws during render, the error surfaces as an uncaught exception rather than as a synchronous throw. By default Cypress fails the test on any uncaught exception, so to assert on a render error you listen for it with [`cy.on('uncaught:exception')`](/llm/markdown/api/cypress-api/catalog-of-events.md#Uncaught-Exceptions) and return `false` to prevent Cypress from failing the test: ``` import { UserProfileComponent } from './user-profile.component' it('surfaces a render error', () => { cy.on('uncaught:exception', (err) => { // Assert on the error thrown during render... expect(err.message).to.include('user is required') // ...and return false so Cypress does not fail the test. return false }) cy.mount(UserProfileComponent) }) ``` ### Testing with a custom `ErrorHandler` The recommended way to observe render errors in Angular is a custom [`ErrorHandler`](https://angular.dev/api/core/ErrorHandler) provider. Angular routes errors thrown during change detection to the `ErrorHandler`, so you can provide one backed by a [`cy.stub()`](/llm/markdown/api/commands/stub.md) and assert it was called: ``` import { ErrorHandler } from '@angular/core' import { ChildWithErrorComponent } from './child-with-error.component' it('routes render errors to the ErrorHandler', () => { const handleError = cy.stub().as('handleError') // A custom ErrorHandler observes the error, but it does NOT stop the error // from propagating to Cypress as an uncaught exception. Cypress fails on // uncaught exceptions by default, so we must still suppress that behavior. cy.on('uncaught:exception', (err) => { // Only suppress the specific error we expect. expect(err.message).to.include('I crashed!') return false }) cy.mount(ChildWithErrorComponent, { providers: [{ provide: ErrorHandler, useValue: { handleError } }], }) cy.get('@handleError').should('have.been.called') }) ``` ## Custom Mount Commands ### Customizing `cy.mount()` By default, `cy.mount()` is a simple passthrough to `mount()`, however, you can customize `cy.mount()` to fit your needs. For instance, you may find yourself doing repetitive work during mounting. In order to reduce boilerplate you may find it useful to create a custom mount command. ### Default Declarations, Providers, or Imports If you find yourself registering a bunch of declarations, providers, or imports in your individual tests, we recommend doing them all within a custom `cy.mount()` command. The overhead is usually minimal for all your tests and it helps keep your spec code clean. Below is a sample that registers several default component declarations while still allowing additional ones to be passed in via the config param. The same pattern can also be applied to providers and module imports. * cypress/support/component.js * cypress/support/component.ts ``` import { mount } from 'cypress/angular' import { ButtonComponent } from 'src/app/button/button.component' import { CardComponent } from 'src/app/card/card.component' const declarations = [ButtonComponent, CardComponent] function customMount(component, config) { if (!config) { config = { declarations } } else { config.declarations = [...(config?.declarations || []), ...declarations] } return mount(component, config) } Cypress.Commands.add('mount', customMount) ``` ``` import { Type } from '@angular/core' import { mount, MountConfig, MountResponse } from 'cypress/angular' import { ButtonComponent } from 'src/app/button/button.component' import { CardComponent } from 'src/app/card/card.component' const declarations = [ButtonComponent, CardComponent] function customMount(component: string | Type, config?: MountConfig) { if (!config) { config = { declarations } } else { config.declarations = [...(config?.declarations || []), ...declarations] } return mount(component, config) } declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Angular Components * @param component Angular Component or template string to mount * @param config Options passed to Angular TestBed */ mount( component: string | Type, config?: MountConfig ): Cypress.Chainable> } } } Cypress.Commands.add('mount', customMount) ``` This custom mount command will allow you to skip manually passing in the `ButtonComponent` and `CardComponent` as declarations into each `cy.mount()` call. --- Source: https://docs.cypress.io/app/component-testing/angular/overview.md Section: Cypress App # Angular Component Testing Cypress Component Testing supports Angular `^21.0.0` and `^22.0.0`. Our testing harness, `cypress/angular`, requires `@angular-devkit/build-angular` to be installed in your project, even if your project is zoneless or is built with `@angular/build`. As of Cypress `16.0.0`, `cypress/angular` supports zoneless testing with no additional configuration. `zone.js` is no longer required. Zoneless is the default in Angular 21 and 22. ## Tutorial Visit the [Getting Started Guide](/llm/markdown/app/component-testing/get-started.md) for a step-by-step tutorial on adding component testing to any project and how to write your first tests. ## Installation To get up and running with Cypress Component Testing in Angular, install Cypress into your project: * npm * Yarn * pnpm * Bun ``` npm install cypress --save-dev ``` ``` yarn add cypress --dev ``` ``` pnpm add --save-dev cypress ``` ``` bun add --dev cypress ``` Open Cypress: * npm * Yarn * pnpm * Bun ``` npx cypress open ``` ``` yarn cypress open ``` ``` pnpm cypress open ``` ``` bunx cypress open ``` Choose Component Testing The Cypress App will guide you through configuring your project. For a step-by-step guide on how to create a component test, refer to the [Getting Started](/llm/markdown/app/component-testing/get-started.md) guide. For usage and examples, visit the [Angular Examples](/llm/markdown/app/component-testing/angular/examples.md) guide. ## Framework Configuration Cypress Component Testing works out of the box with `@angular/cli` projects. Cypress will automatically detect your project is Angular during setup and configure it properly. For a full explanation of how the dev server and bundler work — including automatic config detection and override options — see [Component Testing Configuration — Dev Server and Bundler](/llm/markdown/app/component-testing/component-framework-configuration.md#Dev-Server-and-Bundler). The examples below are for quick reference. ### Angular CLI Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', }, specPattern: '**/*.cy.ts', }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', }, specPattern: '**/*.cy.ts', }, }) ``` #### Options API You can also use the `options` API to provide your own project specific configuration to your `devServer`. The `devServer` configuration receives an `options` property: ``` import { defineConfig } from 'cypress' export default { component: { devServer: { framework: 'angular', bundler: 'webpack', options: { projectConfig: { root: '', sourceRoot: 'apps/my-app', buildOptions: { outputPath: 'dist/my-app', index: 'apps/my-app/src/index.html', main: 'apps/my-app/src/main.ts', polyfills: 'apps/my-app/src/polyfills.ts', tsConfig: 'apps/my-app/tsconfig.app.json', inlineStyleLanguage: 'scss', assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'], styles: ['apps/my-app/src/styles.scss'], scripts: [], buildOptimizer: false, optimization: false, vendorChunk: true, extractLicenses: false, sourceMap: true, namedChunks: true, }, }, }, }, }, } ``` #### Sample Angular Apps * [Angular 22](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/angular) ## Styling Cypress builds your component specs with the Angular build pipeline. When you do **not** provide an `options.projectConfig`, Cypress reads the build options (including `assets`, `styles`, and `stylePreprocessorOptions`) from the default application project in your `angular.json`. When you **do** provide your own [`projectConfig`](#Options-API), it _replaces_ the configuration Cypress detects from `angular.json`, so any build options your styles rely on must be repeated in your `buildOptions`. The two issues below are usually a result of this. ### Sass `@use` / `@import` can't find a stylesheet If your Sass resolves imports through paths configured in `angular.json` under `stylePreprocessorOptions.includePaths`, you may see an error during component testing like: ``` ❌ Can't find stylesheet to import. @use 'mixins' ``` This happens when a custom `projectConfig` omits those include paths. Add the same `stylePreprocessorOptions` to your `buildOptions` so the Sass compiler can resolve the imports: * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', options: { projectConfig: { root: '', sourceRoot: 'src', buildOptions: { styles: ['src/styles.scss'], stylePreprocessorOptions: { includePaths: ['src/styles'], }, }, }, }, }, specPattern: '**/*.cy.ts', }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', options: { projectConfig: { root: '', sourceRoot: 'src', buildOptions: { styles: ['src/styles.scss'], stylePreprocessorOptions: { includePaths: ['src/styles'], }, }, }, }, }, specPattern: '**/*.cy.ts', }, }) ``` ### Tailwind CSS v4 utility classes aren't applied With Tailwind CSS v4, you may find that your own styles load correctly but Tailwind utility classes such as `bg-red-500` have no effect. The class is present on the element in the DevTools inspector, but no rule is generated for it. This is because Tailwind v4 generates utilities through its own build plugin, which is not part of the Angular build pipeline Cypress uses to compile your `styles`. A reliable workaround is to precompile your Tailwind stylesheet into a plain CSS file with the [Tailwind CLI](https://tailwindcss.com/docs/installation/tailwind-cli) and load that compiled file through `buildOptions.styles`. Install the CLI: * npm * yarn * pnpm * bun ``` npm install -D @tailwindcss/cli ``` ``` yarn add -D @tailwindcss/cli ``` ``` pnpm add -D @tailwindcss/cli ``` ``` bun add -d @tailwindcss/cli ``` Compile your stylesheet before opening or running Cypress, then point `buildOptions.styles` at the compiled output: ``` { "scripts": { "build:ct-styles": "npx @tailwindcss/cli -i ./src/styles.scss -o ./cypress/support/compiled-styles.css", "cypress:open": "npm run build:ct-styles && cypress open --component", "cypress:run": "npm run build:ct-styles && cypress run --component" } } ``` * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', options: { projectConfig: { buildOptions: { styles: ['cypress/support/compiled-styles.css'], }, }, }, }, specPattern: '**/*.cy.ts', }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'angular', bundler: 'webpack', options: { projectConfig: { buildOptions: { styles: ['cypress/support/compiled-styles.css'], }, }, }, }, specPattern: '**/*.cy.ts', }, }) ``` ## See also * [Component Testing code coverage](/llm/markdown/app/tooling/code-coverage.md#component-testing-code-coverage) --- Source: https://docs.cypress.io/app/component-testing/component-framework-configuration.md Section: Cypress App # Component Testing Configuration 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: * [React](/llm/markdown/app/component-testing/react/overview.md) * [Vue](/llm/markdown/app/component-testing/vue/overview.md) * [Angular](/llm/markdown/app/component-testing/angular/overview.md) * [Svelte](/llm/markdown/app/component-testing/svelte/overview.md) ## 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: 1. 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). 2. Serving those compiled files over HTTP so Cypress App can load them. 3. 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: 1. Reads `component.devServer` from your Cypress config file. 2. Starts the matching dev server (Vite or Webpack) on an available port. 3. Sets `baseUrl` to `http://localhost:`. 4. 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](/llm/markdown/app/component-testing/react/overview.md#Nextjs). | | `angular` | `webpack` | Supports an `options.projectConfig` override. See [Angular overview](/llm/markdown/app/component-testing/angular/overview.md#Options-API). | 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](/llm/markdown/app/component-testing/custom-frameworks.md). ### 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 } }, }, }, }) ``` ### Resolving import path aliases If your components import modules through path aliases (for example `import Button from '@/components/Button'` or `import { api } from '~/lib/api'`), those aliases must be resolvable by the bundler Cypress uses, otherwise the spec fails to compile with a "module not found" (or "failed to resolve import") error. Cypress does **not** define any aliases of its own. It resolves them exactly the way your dev server does, using the bundler config it detects or the one you pass in: * **Vite**: aliases come from the `resolve.alias` of the `vite.config` Cypress detects, or the `viteConfig` you pass to `devServer`. * **Webpack**: aliases come from the `resolve.alias` of the `webpack.config` Cypress detects, or the `webpackConfig` you pass to `devServer`. So if your aliases are declared in a standalone `vite.config` or `webpack.config` at (or above) your project root, Cypress picks them up automatically and no extra configuration is needed. #### Meta-frameworks that own the bundler config Some meta-frameworks (such as **Nuxt**) configure Vite internally rather than through a standalone `vite.config` file. Cypress only reads a discoverable `vite.config`/`webpack.config`. It does **not** execute `nuxt.config` (or similar) to extract the bundler settings those frameworks generate at runtime. As a result, framework-provided aliases like Nuxt's `@/` and `~/` are not visible to Cypress and must be declared explicitly via `viteConfig`: * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') const { fileURLToPath } = require('url') module.exports = defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', viteConfig: { resolve: { alias: { '@': fileURLToPath(new URL('./', import.meta.url)), '~': fileURLToPath(new URL('./', import.meta.url)), }, }, }, }, }, }) ``` ``` import { defineConfig } from 'cypress' import { fileURLToPath } from 'url' export default defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', viteConfig: { resolve: { alias: { '@': fileURLToPath(new URL('./', import.meta.url)), '~': fileURLToPath(new URL('./', import.meta.url)), }, }, }, }, }, }) ``` The aliases you pass are merged with Cypress's own Vite settings, so you only need to declare the ones your components rely on. The same applies to Webpack meta-frameworks: declare the aliases under `webpackConfig.resolve.alias`. #### Aliases defined in `tsconfig.json` Aliases declared only under `compilerOptions.paths` in `tsconfig.json` are a TypeScript type-checking feature. Neither Vite nor Webpack reads them when bundling, so Cypress does not resolve them either. To make `tsconfig` paths work at bundle time, add the matching plugin to the config you give Cypress: * **Vite**: use [`vite-tsconfig-paths`](https://www.npmjs.com/package/vite-tsconfig-paths) in your `vite.config` (or in the `viteConfig` you pass to `devServer`). * **Webpack**: use [`tsconfig-paths-webpack-plugin`](https://www.npmjs.com/package/tsconfig-paths-webpack-plugin) under `resolve.plugins` in your `webpack.config` (or the `webpackConfig` you pass to `devServer`). This is the same plugin your app already needs to resolve `tsconfig` paths at build time, so reusing your existing bundler config is usually enough. ### 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](/llm/markdown/app/references/configuration.md#component) 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](/llm/markdown/app/references/configuration.md#component) 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( '', `` ) 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](https://github.com/cypress-io/cypress/blob/466155c2125476374d9f9549530f67d0c6354a41/npm/vite-dev-server/src/plugins/cypress.ts#L82-L92) 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' ) // `onSpecWindow` expects an array of functions that each return a dynamic // `import()`. Cypress invokes them in order, so the support file (if any) // must be added before the spec. const importsToLoad = [] // 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'), '' ) importsToLoad.push(() => import(`${devServerPublicPathRoute}${relative}`)) } // load the spec - you can extend the load function to also load css const { relative } = CypressInstance.spec importsToLoad.push(() => import(`${devServerPublicPathRoute}/${relative}`)) // trigger loading the imports CypressInstance.onSpecWindow(window, importsToLoad) // 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.](https://github.com/cypress-io/cypress/blob/develop/npm/vite-dev-server/client/initCypressTests.js) The `devServerEvents` event emitter is used to communicate compile lifecycle events between your server and Cypress: * Emit `dev-server:compile:success` to notify Cypress that a build finished and tests can run. * Listen for `dev-server:specs:changed` to 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](/llm/markdown/app/references/configuration.md). --- Source: https://docs.cypress.io/app/component-testing/custom-frameworks.md Section: Cypress App # Custom component testing frameworks Cypress Component Testing includes [official support](/llm/markdown/app/component-testing/get-started.md#Supported-Frameworks) for many popular libraries and frameworks such as [React](/llm/markdown/app/component-testing/react/overview.md), [Angular](/llm/markdown/app/component-testing/angular/overview.md), and [Vue](/llm/markdown/app/component-testing/vue/overview.md). All officially supported libraries feature a first class onboarding experience, where we detect and scaffold the correct files, and a framework-specific `mount` adapter to render your components. We call this collection of features a _Framework Definition_ since it defines the requirements for a library or framework to work in Cypress. If your favorite library isn't featured, don't worry - we expose the same API we use internally for the Cypress community to define their own Framework Definitions. In this guide, you'll learn how to author a Framework Definition, which will enjoy the same polished onboarding experience as our officially supported frameworks. ## Concepts There are a few requirements for authoring a Framework Definition. * [Definition File](#Framework-Definition) (we recommending naming this `definition.cjs`) * [Mount Adapter](#Mount-Adapter) (we recommending naming this `index.mjs`) * A [`package.json`](#packagejson) with the correct conventions The Definition is required when users configure Component Testing for the first time. The Mount adapter is used to render components when writing tests. To simplify this process, we recommend starting development using [our official template](https://github.com/cypress-io/cypress-ct-definition-template). ## Framework Definition Below is a minimal Framework Definition. Note that `defineFrameworkDefinition` is purely for type safety, similar to `defineConfig` in `cypress.config`. There is one important convention; the `type` key in `defineFrameworkDefinition` should match the name of your package on npm, and should be named using one of the following conventions: * `cypress-ct-*` * `@organization/cypress-ct-*` Some examples of valid names include: * `cypress-ct-react-js` * `cypress-ct-svelte-testing` * `@cypress/cypress-ct-react` * `@angular/cypress-ct-angular` When configuring a project to use Component Testing, Cypress will load any dependencies following this naming convention from the project's `node_modules` and present them as framework options. A simple example of a Framework Definition for the [Solid.js](https://www.solidjs.com/) library is shown below. We generally recommend naming this `definition.cjs`. In our [official template](https://github.com/cypress-io/cypress-ct-definition-template), this file is at the root level of the package. ``` const { defineFrameworkDefinition } = require('cypress') const solidDep = { // Unique, semantic identifier. type: 'solid-js', // Human readable name. name: 'Solid', // Package name install from `npm`. package: 'solid-js', /** * Similar to package, but can include a version or tag. * Used during setup to generate an install command for users. * Eg: `solid-js@next` */ installer: 'solid-js', // Human readable description. description: 'A declarative, efficient, and flexible JavaScript library for building user interfaces.', // Minimum supported version. minVersion: '^1.6.0', } /** * Similar to above. Create an smooth, seamless setup experience * by ensuring the user has all the necessary dependencies. * @type {Cypress.CypressComponentDependency} */ const solidVitePlugin = { type: 'solid-js-vite-plugin', name: 'Vite Plugin Solid', package: 'vite-plugin-solid', installer: 'vite-plugin-solid', description: 'A simple integration to run solid-js with vite', minVersion: '^1.6.0 || ^2.0.0', } /** * The actual definition. */ module.exports = defineFrameworkDefinition({ /** * This should match the `npm` package name. * The convention required to ensure your Definition is processed * by Cypress is `cypress-ct-*` for global packages, or * `@org/cypress-ct-*` for organization level packages. */ type: '@lmiller1990/cypress-ct-solid-js', /** * The label that shows up when configuring Component Testing * for the first time. */ name: 'Solid.js', /** * Supported bundlers. Can be "webpack" and/or "vite". * In this example we only support Solid.js with Vite. */ supportedBundlers: ['vite'], /** * Used by Cypress to automatically detect the correct Framework Definition * based on the user's project. * In this example, if a module matching `solidDep` * is found in the user's project, * Solid.js will automatically be selected when configuring Component Testing. */ detectors: [solidDep], /** * Supply a set of dependencies a project should have to use this Framework Definition. The user will be prompted to install them if they are not found. * Optionally, supply different dependencies based on the chosen bundler. */ dependencies: (bundler) => { return [solidDep, solidVitePlugin] }, /** * An SVG icon. Shown when configuring Component Testing for the first time. * Optional, but good for branding your Framework Definition. */ icon: ` `, }) ``` Our Framework Definition shows up in Cypress! It has the "community" label, indicating it's a third party definition. We defined the `dependencies`, which are also correctly handled - we haven't installed them all, so Cypress is prompting us to do so: ## Mount Adapter The second part of defining a Framework Definition is the Mount Adapter. This is the function that renders the component in your tests using `cy.mount()`. By default, Cypress will look for this as a `mount` function that is a **named export** from the package. This should be written in a `index.mjs` file. This example is for a [Solid.js](https://www.solidjs.com/) mount adapter: ``` import { getContainerEl, setupHooks } from '@cypress/mount-utils' import { render } from 'solid-js/web' let dispose function cleanup() { dispose?.() } /** * @param {() => JSX.Element} - component to render */ export function mount(component, options = {}) { // Retrieve root DOM element that Cypress has prepared for this test const root = getContainerEl() dispose = render(() => component, root) // Wait until next microtick to ensure any async render logic has executed return cy.wait(0, { log: false }).then(() => { if (options.log !== false) { Cypress.log({ name: 'mount', message: 'Mounted component', }) } }) } // Cleanup between each test setupHooks(cleanup) ``` This is different for each library, but the concept is the same - identify how to mount or render a component in your library, and implement it in a function named `mount` in `index.mjs`. When a user configures Component Testing with your Framework Definition we automatically configure a `cy.mount` command using your `mount` function in the Component Testing `supportFile`: * cypress/support/component.js * cypress/support/component.ts ``` import { mount } from '@lmiller1990/cypress-ct-solid-js' Cypress.Commands.add('mount', mount) ``` ``` import { mount } from '@lmiller1990/cypress-ct-solid-js' declare global { namespace Cypress { interface Chainable { mount: typeof mount } } } Cypress.Commands.add('mount', mount) ``` ## package.json The final thing you need is a correctly configured `package.json`. This example was created using [our official template](https://github.com/cypress-io/cypress-ct-definition-template). There are two fields of note: * `name`: Your package name which must follow the `cypress-ct-*`/`@org/cypress-ct-*` convention * `exports`: Object referencing the two files we created to comprise the Framework Definition. The `node` entry points to the Definition file, and the `default` entry points to the Mount Adapter: ``` { "name": "@lmiller1990/cypress-ct-solid-js", "version": "0.0.4", "description": "Example Framework Definition for Cypress and Solid.js", "exports": { "node": "./definition.cjs", "default": "./index.mjs" }, "files": [ "package.json", "definition.cjs", "index.mjs" ], "dependencies": { "@cypress/mount-utils": "^4.0.0" }, "devDependencies": { "solid-js": "^1.6.0" }, "peerDependencies": { "solid-js": "^1.6.0" "cypress": "^12.7.0" } } ``` ## Testing If you develop the Framework Definition file using TypeScript, for example by using [our official template](https://github.com/cypress-io/cypress-ct-definition-template), as long as you don't have any compile time errors, everything should work as expected. If you do run into unexpected behavior, please [file an issue](https://github.com/cypress-io/cypress/issues). Mount Adapters can be more complex to test. In general, we recommend testing them in the same way users consume them - using Cypress Component Testing. A minimal test suite for a simple Solid.js Mount Adapter can be found [here](https://github.com/lmiller1990/cypress-ct-solid-js/tree/main/example-project). Alternatively, take a look at our official [React](https://github.com/cypress-io/cypress/tree/develop/npm/react) and [Angular](https://github.com/cypress-io/cypress/tree/develop/npm/angular) Mount Adapters, both of which have extensive test suites. ## Publishing on npm That's it! Publish your Framework Definition on npm and start using it. ## Available Framework Definitions You can find a list of available Framework Definitions [here](/llm/markdown/app/component-testing/get-started.md#Supported-Frameworks). If you have created a Framework Definition we would be delighted to mention it in our documentation so other Cypress users on the same framework can find it. [Please submit a Pull Request](https://github.com/cypress-io/cypress-documentation/blob/main/CONTRIBUTING.md)! --- Source: https://docs.cypress.io/app/component-testing/get-started.md Section: Cypress App # Cypress Component Testing Cypress Component Testing mounts your components directly in a **real browser** — not a simulated DOM — so you test them exactly as they will behave for your users. Every component renders visually during the test run, and you can use browser DevTools to inspect, interact, and debug just as you would during development. ## Why Cypress for Component Testing? Teams that adopt Cypress for component testing typically come looking for faster feedback on their UI components. They stay because Cypress brings together capabilities that would otherwise require assembling several separate tools: * **See your component in action.** Components render visually inside the Cypress App as each test runs. You can interact with them manually, inspect elements with DevTools, and use [Time Travel](/llm/markdown/app/core-concepts/open-mode.md#Command-Log) to step back through exactly what happened at each point in the test — no guessing, no `console.log` archaeology. * **Write less test boilerplate.** [Automatic waiting](/llm/markdown/app/core-concepts/introduction-to-cypress.md#Cypress-is-Not-Like-jQuery) means your assertions run only after your component has rendered and updated. There is no need for `waitFor`, `act()`, or manual timeouts. You describe what should be true; Cypress waits until it is. * **Use powerful testing primitives — built in.** [Spies and stubs](/llm/markdown/app/guides/stubs-spies-and-clocks.md) to verify event handlers and isolate dependencies. [Network interception](/llm/markdown/app/guides/network-requests.md) to test how your component handles any server response without a running backend. [Clock control](/llm/markdown/api/commands/clock.md) to test time-sensitive logic instantly, without sleeping. * **One project, one quality signal.** Component tests live in the same Cypress project as your end-to-end tests. When connected to [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md), results, flake patterns, and [coverage data](/llm/markdown/ui-coverage/get-started/introduction.md) flow into a single view — so your team has clear evidence of quality at every layer before a release ships. Catching a component bug in isolation is far cheaper than catching the same bug in an end-to-end test or, worse, in production. Teams that test components with Cypress close that feedback loop earlier, reduce the cost of fixing defects, and ship with more confidence. ## Configuring Component Testing Assuming you've successfully [installed Cypress](/llm/markdown/app/get-started/install-cypress.md) and [opened Cypress](/llm/markdown/app/get-started/open-the-app.md), now it's time to set up component testing. The Cypress App will guide you through configuring your project. ### Supported Frameworks Cypress currently has official mounting libraries for [React](/llm/markdown/app/component-testing/react/overview.md), [Angular](/llm/markdown/app/component-testing/angular/overview.md), [Vue](/llm/markdown/app/component-testing/vue/overview.md), and [Svelte](/llm/markdown/app/component-testing/svelte/overview.md) and support for the following development servers and frameworks: | Framework | UI Library | Bundler | | --- | --- | --- | | [React with Vite](/llm/markdown/app/component-testing/react/overview.md#React-with-Vite) | React 18-19 | Vite 8 | | [React with Webpack](/llm/markdown/app/component-testing/react/overview.md#React-with-Webpack) | React 18-19 | Webpack 5 | | [Next.js 15-16](/llm/markdown/app/component-testing/react/overview.md#Nextjs) | React 18-19 | Webpack 5 | | [Vue with Vite](/llm/markdown/app/component-testing/vue/overview.md#Vue-with-Vite) | Vue 3 | Vite 8 | | [Vue with Webpack](/llm/markdown/app/component-testing/vue/overview.md#Vue-with-Webpack) | Vue 3 | Webpack 5 | | [Angular](/llm/markdown/app/component-testing/angular/overview.md#Framework-Configuration) | Angular 21-22 | Webpack 5 | | [Svelte with Vite](/llm/markdown/app/component-testing/svelte/overview.md#Svelte-with-Vite) Alpha | Svelte 5 | Vite 8 | | [Svelte with Webpack](/llm/markdown/app/component-testing/svelte/overview.md#Svelte-with-Webpack) Alpha | Svelte 5 | Webpack 5 | The following integrations are built and maintained by Cypress community members. | Framework | UI Library | Bundler | | --- | --- | --- | | [Qwik](https://github.com/qwikifiers/cypress-qwik) Community | Qwik | Vite | | [Lit](https://github.com/redfox-mx/cypress-lit) Community | Lit | Vite | ### Select Testing Type Whenever you run Cypress for the first time, the app will prompt you to set up either E2E Testing or Component Testing. Click on "Component Testing" to start the configuration wizard. For more information on how to choose a testing type, we recommend this [Testing Types Guide](/llm/markdown/app/core-concepts/testing-types.md) Choose Component Testing ### Project Setup The Project Setup screen will automatically detect your framework and bundler. In this example we will use [React](https://react.dev/) and [Vite](https://vitejs.dev/). Click "Next Step" to continue. React and Vite are automatically detected ### Install Dependencies The next screen checks that all the required dependencies are installed. All the items should have green checkboxes on them, indicating everything is good, so click "Continue". All necessary dependencies are installed ### Config Files Next, Cypress generates all the necessary configuration files and gives you a list of all the changes it made to your project. Click "Continue". The most important generated setting is `component.devServer` in `cypress.config`, which tells Cypress which UI framework and bundler to use. For most projects, the scaffolded values are all you need. See [Dev Server and Bundler](/llm/markdown/app/component-testing/component-framework-configuration.md#Dev-Server-and-Bundler) for how this works and how to customize it. The Cypress launchpad will scaffold all of these files for you ### Choose A Browser After setting up component testing, you will be at the browser selection screen. Pick the browser of your choice and click the "Start Component Testing" button to open the Cypress App. Choose your browser ## Writing Your First Test At this point, your project is configured to use Cypress Component Testing. However, when the app appears, it won't find any specs because we haven't created any yet. Now we need write our first Component Test. ### Frameworks Cypress currently has official mounting libraries for [React](/llm/markdown/app/component-testing/react/overview.md), [Angular](/llm/markdown/app/component-testing/angular/overview.md), [Vue](/llm/markdown/app/component-testing/vue/overview.md), and [Svelte](/llm/markdown/app/component-testing/svelte/overview.md). In addition to our official framework support there are many community plugins such as [Qwik](https://github.com/qwikifiers/cypress-qwik) and [Lit](https://github.com/redfox-mx/cypress-lit). ### Your First Component Test Though every framework is different and has its own unique nuances, in general, writing tests is very similar. Let's look at how a basic test is written for a `StepperComponent`. Let's assume the Stepper Component consists of two `buttons`, one used to **decrement** the counter and one to **increment** it as well as a `span` tag that sits in the middle of the buttons to show the current value of the counter. To see examples of the Stepper Component and its tests in each Framework, visit our [Component Testing Quick Start Apps Repo](https://github.com/cypress-io/component-testing-quickstart-apps) * React * Angular * Vue * Svelte ``` import React from 'react' import Stepper from './Stepper' describe('', () => { it('mounts', () => { // see: https://on.cypress.io/mounting-react cy.mount() }) }) ``` ``` import { StepperComponent } from './stepper.component' describe('StepperComponent', () => { it('mounts', () => { // see: https://on.cypress.io/mounting-angular cy.mount(StepperComponent) }) }) ``` ``` import Stepper from './Stepper.vue' describe('', () => { it('mounts', () => { // see: https://on.cypress.io/mounting-vue cy.mount(Stepper) }) }) ``` ``` import Stepper from './Stepper.svelte' describe('Stepper', () => { it('mounts', () => { // see: https://on.cypress.io/mounting-svelte cy.mount(Stepper) }) }) ``` ### Test Explanation Let's break down the spec. First, we import the `Stepper` component. Then, we organize our tests using the functions `describe` and `it`, which allows us to group tests into sections by using method blocks. These are global functions provided by Cypress, which means you don't have to import them directly to use them. The top-level `describe` block will be the container for all our tests in a file, and each `it` represents an individual test. The `describe` function takes two parameters, the first of which is the name of the test suite, and the second is a function that will execute the tests. We defined a test using the `it` function inside `describe`. The first parameter to `it` is a brief description of the spec, and the second parameter is a function that contains the test code. In our example above, we only have one test, but soon we'll see how we can add multiple `it` blocks inside of a `describe` for a series of tests. The [cy.mount()](/llm/markdown/api/commands/mount.md) method will mount our component into the test app so we can begin running tests against it. Now it's time to see the test in action. ## Running the Test Switch back to the browser you opened for testing, and you should now see the `Stepper` Component in the spec list. Click it to see the spec execute. Our first test verifies the component can mount in its default state without any errors. If there is a runtime error during test execution, the test will fail, and you will see a stack trace pointing to the source of the problem. A basic test like the one above is an excellent way to start testing a component. Cypress renders your component in a real browser, and you can use all the techniques/tools you would normally during development, such as interacting with the component in the test runner, and using the browser dev tools to inspect and debug both your tests and the component's code. Feel free to play around with the `Stepper` component by interacting with the increment and decrement buttons. Now that the component is mounted, our next step is to test that the behavior of the component is correct. ### Selectors & Assertions By default, the Stepper's counter is initialized to "0". We can override that value by specifying an initial count. Let's write a couple of tests that will verify both these states. To do so, we will use a selector to access the `span` element that contains the counter, and then assert that the text value of the element is what we expect it to be. There are various ways to select items from the DOM using Cypress. We will use [cy.get()](/llm/markdown/api/commands/get.md), which allows us to pass in a CSS-like selector. After we "get" the element, we use the [should](/llm/markdown/api/commands/should.md) assertion method to verify it has the correct text value. Add the following test inside the `describe` block, right below the first test: * React * Angular * Vue * Svelte ``` it('stepper should default to 0', () => { cy.mount() cy.get('span').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(StepperComponent) cy.get('span').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(Stepper) cy.get('span').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(Stepper) cy.get('span').should('have.text', '0') }) ``` When you go back to the test runner, you should see the test pass. In the above test, we select the element by passing in "span" to `cy.get()`, which will select all `span` tags in our component. We only have one `span` currently, so this works. However, if our component evolves and we add another `span`, then this test could start to fail. We should use a selector that will be less brittle to future changes. In the `Stepper` component, the `span` tag has a `data-cy` attribute on it: * React * Angular * Vue * Svelte ``` {count} ``` ``` {{ count }} ``` ``` {{ count }} ``` ``` {count} ``` We assign a unique id to the `data-cy` attribute that we can use for testing purposes. Update the test to use a CSS attribute selector to `cy.get()`: * React * Angular * Vue * Svelte ``` it('stepper should default to 0', () => { cy.mount() cy.get('[data-cy=counter]').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(StepperComponent) cy.get('[data-cy=counter]').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(Stepper) cy.get('[data-cy=counter]').should('have.text', '0') }) ``` ``` it('stepper should default to 0', () => { cy.mount(Stepper) cy.get('[data-cy=counter]').should('have.text', '0') }) ``` The test will still pass as expected, and our selector is now future-proof. For more info on writing good selectors, see our guide [Selector Best Practices](/llm/markdown/app/core-concepts/best-practices.md#Selecting-Elements). ### Passing Props to Components We should also have a test to ensure the `count` prop sets the count to something else besides its default value of "0". We can pass in props to the `Stepper` component like so: * React * Angular * Vue * Svelte ``` it('supports a "count" prop to set the value', () => { cy.mount() cy.get('[data-cy=counter]').should('have.text', '100') }) ``` ``` it('supports a "count" prop to set the value', () => { cy.mount(StepperComponent, { componentProperties: { count: 100, }, }) cy.get('[data-cy=counter]').should('have.text', '100') }) ``` ``` it('supports a "count" prop to set the value', () => { cy.mount(Stepper, { props: { count: 100 } }) cy.get('[data-cy=counter]').should('have.text', '100') }) ``` ``` it('supports a "count" prop to set the value', () => { cy.mount(Stepper, { props: { count: 100 } }) cy.get('[data-cy=counter]').should('have.text', '100') }) ``` ### Testing Interactions We mounted and selected the element in the above tests but didn't interact with it. We should also test that the value of the counter changes when a user clicks the "increment" and "decrement" buttons. To do so, we will interact with the component by using the [click()](/llm/markdown/api/commands/click.md) command, which clicks a DOM element just like a real user would. Add the following tests: * React * Angular * Vue * Svelte ``` it('when the increment button is pressed, the counter is incremented', () => { cy.mount() cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') }) it('when the decrement button is pressed, the counter is decremented', () => { cy.mount() cy.get('[data-cy=decrement]').click() cy.get('[data-cy=counter]').should('have.text', '-1') }) ``` ``` it('when the increment button is pressed, the counter is incremented', () => { cy.mount(StepperComponent) cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') }) it('when the decrement button is pressed, the counter is decremented', () => { cy.mount(StepperComponent) cy.get('[data-cy=decrement]').click() cy.get('[data-cy=counter]').should('have.text', '-1') }) ``` ``` it('when the increment button is pressed, the counter is incremented', () => { cy.mount(Stepper) cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') }) it('when the decrement button is pressed, the counter is decremented', () => { cy.mount(Stepper) cy.get('[data-cy=decrement]').click() cy.get('[data-cy=counter]').should('have.text', '-1') }) ``` ``` it('when the increment button is pressed, the counter is incremented', () => { cy.mount(Stepper) cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') }) it('when the decrement button is pressed, the counter is decremented', () => { cy.mount(Stepper) cy.get('[data-cy=decrement]').click() cy.get('[data-cy=counter]').should('have.text', '-1') }) ``` When you view the results of each of the tests, you will see that the counter is now "1" in the **increment** test, and "-1" in the **decrement** test. Not only did our tests pass, but we can visually see the results in a browser! ## Testing Components with Events All the state of the `Stepper` Component (ie: the count) is handled internally in the component and then consumers are then alerted to changes to the state. As the developer of the `Stepper` Component, you want to make sure when the end-user clicks the **increment** and **decrement** buttons, that the appropriate event is triggered with the proper values in the consuming component. ### Using Spies We can use [Cypress Spies](/llm/markdown/app/guides/stubs-spies-and-clocks.md#Spies) to validate these events are being called correctly. A spy is a special function that keeps track of how many times it was called and any parameters that it was called with. We can then assign a spy to our event, interact with the component, and then query the spy to validate it was called with the parameters we expect. Let's set up the spies and bind them to the component: * React * Angular * Vue * Svelte ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount() cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(StepperComponent, { componentProperties: { change: { emit: onChangeSpy, } as any, }, }) cy.get('[data-cy=increment]').click() cy.get('@changeSpy').should('have.been.calledWith', 1) }) ``` ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(Stepper, { props: { onChange: onChangeSpy } }) cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(Stepper, { props: { onChange: onChangeSpy } }) cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` First, we create a new spy by calling the `cy.spy()` method. We pass in a string that gives the spy an [alias](/llm/markdown/app/core-concepts/variables-and-aliases.md), which assigns the spy a name by which we can reference it later. In `cy.mount()`, we initialize the component and pass the spy into it. After that, we click the increment button. The next line is a bit different. We've seen how we can use the `cy.get()` method to select elements, but we can also use it to grab any aliases we've set up previously. We use `cy.get()` to grab the alias to the spy (by prepending an "@" to the alias name). We assert that the method was called with the expected value. With that, the `Stepper` component is well tested. Nice job! ## What's Next? Congratulations, you covered the basics for Component Testing with Cypress! To learn more about testing with Cypress, check out the [Introduction to Cypress](/llm/markdown/app/core-concepts/introduction-to-cypress.md) guide. To track which parts of your components are exercised by your tests, set up [code coverage for component tests](/llm/markdown/app/tooling/code-coverage.md#component-testing-code-coverage). Writing component tests with an AI coding tool? The `/cypress-author` skill applies Cypress best practices automatically. See [Cypress AI Skills](/llm/markdown/app/tooling/ai-skills.md). --- Source: https://docs.cypress.io/app/component-testing/react/api.md Section: Cypress App # React API The `cypress/react` module provides the following methods and interfaces for mounting and testing React components. ## mount ``` import { mount } from 'cypress/react' ```
DescriptionMounts a React component into the DOM.
Signature

mount(jsx: React.ReactNode, options?: MountOptions, rerenderKey?: string): Cypress.Chainable<MountReturn>

ReturnsCypress.Chainable<MountReturn>
mount Parameters
NameTypeDescription
jsxReact.JSX.ElementThe React component to mount.
optionsMountOptions (optional)The options for mounting the component
rerenderKeystring (optional)A key to use to force a rerender.
### Example ``` import { mount } from '@cypress/react' import { Stepper } from './Stepper' it('mounts', () => { mount() cy.get('[data-cy=increment]').click() cy.get('[data-cy=counter]').should('have.text', '1') } ``` ## getContainerEl
DescriptionGets the root element used to mount the component.
Signature() => HTMLElement
ReturnsHTMLElement
## Interfaces ### MountOptions
members
NameTypeDescription
aliasstring (optional)
ReactDomMountReactComponentOptions.ReactDom (optional)
logboolean (optional)Log the mounting command into Cypress Command Log, true by default
strictboolean (optional)

Render component in React strict mode It activates additional checks and warnings for child components.

### MountReturn Type that the `mount` function yields
members
NameTypeDescription
componentReact.ReactNode

Render component in React strict mode It activates additional checks and warnings for child components.

rerender

(component: React.ReactNode) => globalThis.Cypress.Chainable<MountReturn>

The component that was rendered.
--- Source: https://docs.cypress.io/app/component-testing/react/examples.md Section: Cypress App # React Examples The first step in testing a component is to mount it. This renders the component into a testbed and enable's the use of the Cypress API to select elements, interact with them, and run assertions. To mount a React component, import the component into your spec and pass the component to the `cy.mount` command: ``` import { Stepper } from './stepper' it('mounts', () => { cy.mount() //Stepper should have initial count of 0 (default) cy.get('[data-cy=counter]').should('have.text', '0') }) ``` ## Passing Data to a Component You can pass props to a component by setting them on the JSX passed into `cy.mount()`: ``` it('mounts', () => { cy.mount() //Stepper should have initial count of 100 cy.get('[data-cy=counter]').should('have.text', '100') }) ``` ## Testing Event Handlers Pass a Cypress [spy](/llm/markdown/app/guides/stubs-spies-and-clocks.md#Spies) to an event prop and validate it was called: ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount() cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` ## Testing Error States `cy.mount()` is an asynchronous Cypress command: it _enqueues_ the mount and returns immediately, before the component ever renders. When a component throws during render, the error surfaces as an uncaught exception rather than as a synchronous throw. By default Cypress fails the test on any uncaught exception, so to assert on a render error you listen for it with [`cy.on('uncaught:exception')`](/llm/markdown/api/cypress-api/catalog-of-events.md#Uncaught-Exceptions) and return `false` to prevent Cypress from failing the test: ``` import { UserProfile } from './UserProfile' it('surfaces a render error', () => { cy.on('uncaught:exception', (err) => { // Assert on the error thrown during render... expect(err.message).to.include('user is required') // ...and return false so Cypress does not fail the test. return false }) cy.mount() }) ``` ### Testing Error Boundaries The recommended way to handle render errors in React is an [Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary), which renders fallback UI via `getDerivedStateFromError()`. You can mount a component wrapped in your Error Boundary and assert that the fallback UI is displayed: ``` // ErrorBoundary.cy.jsx import { ErrorBoundary } from './ErrorBoundary' const errorMessage = 'I crashed!' const ChildWithError = () => { throw new Error(errorMessage) } it('displays the fallback UI on error', () => { // An Error Boundary renders fallback UI, but it does NOT stop the error // from propagating to Cypress as an uncaught exception. Cypress fails on // uncaught exceptions by default, so we must still suppress that behavior. cy.on('uncaught:exception', (err) => { // Only suppress the specific error we expect. expect(err.message).to.include(errorMessage) return false }) cy.mount( ) cy.get('header h1').should('contain', 'Something went wrong.') }) ``` Where `ErrorBoundary` renders fallback UI from `getDerivedStateFromError()`: ``` // ErrorBoundary.jsx import React from 'react' export class ErrorBoundary extends React.Component { constructor(props) { super(props) this.state = { error: null } } static getDerivedStateFromError(error) { return { error } } render() { const { name } = this.props const { error } = this.state if (error) { return (

Something went wrong.

{`${name} failed to load`}

) } return this.props.children } } ``` ## Custom Mount Commands ### Customizing `cy.mount()` By default, `cy.mount()` is a simple passthrough to `mount()`, however, you can customize `cy.mount()` to fit your needs. For instance, if you are using providers or other global app-level setups in your React app, you can configure them here. Below are a few examples that demonstrate using a custom mount command. These examples can be adjusted for most other providers that you will need to support. ### React Router If you have a component that consumes a hook or component from [React Router](https://reactrouter.com/), make sure the component has access to a React Router provider. Below is a sample mount command that uses `MemoryRouter` to wrap the component. * cypress/support/component.jsx * cypress/support/component.tsx ``` import { mount } from 'cypress/react' import { MemoryRouter } from 'react-router-dom' Cypress.Commands.add('mount', (component, options = {}) => { const { routerProps = { initialEntries: ['/'] }, ...mountOptions } = options const wrapped = {component} return mount(wrapped, mountOptions) }) ``` ``` import { mount, MountOptions, MountReturn } from 'cypress/react' import { MemoryRouter, MemoryRouterProps } from 'react-router-dom' declare global { namespace Cypress { interface Chainable { /** * Mounts a React node * @param component React Node to mount * @param options Additional options to pass into mount */ mount( component: React.ReactNode, options?: MountOptions & { routerProps?: MemoryRouterProps } ): Cypress.Chainable } } } Cypress.Commands.add('mount', (component, options = {}) => { const { routerProps = { initialEntries: ['/'] }, ...mountOptions } = options const wrapped = {component} return mount(wrapped, mountOptions) }) ``` To set up certain scenarios, pass in props that will get passed to `MemoryRouter` in the options. Below is an example test that ensures an active link has the correct class applied to it by initializing the router with `initialEntries` pointed to a particular route: ``` import { Navigation } from './Navigation' it('home link should be active when url is "/"', () => { // No need to pass in custom initialEntries as default url is '/' cy.mount() cy.get('a').contains('Home').should('have.class', 'active') }) it('login link should be active when url is "/login"', () => { cy.mount(, { routerProps: { initialEntries: ['/login'], }, }) cy.get('a').contains('Login').should('have.class', 'active') }) ``` ### Redux To use a component that consumes state or actions from a [Redux](https://react-redux.js.org/) store, create a `mount` command that will wrap your component in a Redux Provider: * cypress/support/component.jsx * cypress/support/component.tsx ``` import { mount } from 'cypress/react' import { Provider } from 'react-redux' import { getStore } from '../../src/store' Cypress.Commands.add('mount', (component, options = {}) => { // Use the default store if one is not provided const { reduxStore = getStore(), ...mountOptions } = options const wrapped = {component} return mount(wrapped, mountOptions) }) ``` ``` import { mount, MountOptions, MountReturn } from 'cypress/react' import { Provider } from 'react-redux' import { EnhancedStore } from '@reduxjs/toolkit' import { getStore } from '../../src/store' import { RootState } from './src/StoreState' declare global { namespace Cypress { interface Chainable { /** * Mounts a React node * @param component React Node to mount * @param options Additional options to pass into mount */ mount( component: React.ReactNode, options?: MountOptions & { reduxStore?: EnhancedStore } ): Cypress.Chainable } } } Cypress.Commands.add('mount', (component, options = {}) => { const { reduxStore = getStore(), ...mountOptions } = options const wrapped = {component} return mount(wrapped, mountOptions) }) ``` The options param can have a store that is already initialized with data: ``` import { getStore } from '../redux/store' import { setUser } from '../redux/userSlice' import { UserProfile } from './UserProfile' it('User profile should display user name', () => { const user = { name: 'test person' } // getStore is a factory method that creates a new store const store = getStore() // setUser is an action exported from the user slice store.dispatch(setUser(user)) cy.mount(, { reduxStore: store }) cy.get('div.name').should('have.text', user.name) }) ``` The `getStore` method is a factory method that initializes a new Redux store. It is important that the store be initialized with each new test to ensure changes to the store don't affect other tests. --- Source: https://docs.cypress.io/app/component-testing/react/overview.md Section: Cypress App # React Component Testing Cypress Component Testing currently supports React 18 and 19 with the following frameworks: * [React with Vite](#React-with-Vite) * [React with Webpack](#React-with-Webpack) * [Next.js](#Nextjs) ## Tutorial Visit the [Getting Started Guide](/llm/markdown/app/component-testing/get-started.md) for a step-by-step tutorial on adding component testing to any project and how to write your first tests. ## Installation To get up and running with Cypress Component Testing in React, install Cypress into your project: * npm * Yarn * pnpm * Bun ``` npm install cypress --save-dev ``` ``` yarn add cypress --dev ``` ``` pnpm add --save-dev cypress ``` ``` bun add --dev cypress ``` Open Cypress: * npm * Yarn * pnpm * Bun ``` npx cypress open ``` ``` yarn cypress open ``` ``` pnpm cypress open ``` ``` bunx cypress open ``` Choose Component Testing The Cypress Launchpad will guide you through configuring your project. For a step-by-step guide on how to create a component test, refer to the [Getting Started](/llm/markdown/app/component-testing/get-started.md) guide. For usage and examples, visit the [React Examples](/llm/markdown/app/component-testing/react/examples.md) guide. ## Framework Configuration Cypress Component Testing works out of the box with [Vite](https://vitejs.dev/), [Next.js](https://nextjs.org/), and a custom [Webpack](https://webpack.js.org/) config. Cypress will automatically detect one of these frameworks during setup and configure them properly. For a full explanation of how the dev server and bundler work — including automatic config detection and override options — see [Component Testing Configuration — Dev Server and Bundler](/llm/markdown/app/component-testing/component-framework-configuration.md#Dev-Server-and-Bundler). The examples below are for quick reference. ### React with Vite Cypress Component Testing works with React apps that use Vite `8.x` as the bundler. #### Vite Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') const customViteConfig = require('./customConfig') module.exports = defineConfig({ component: { devServer: { framework: 'react', bundler: 'vite', // optionally pass in vite config viteConfig: customViteConfig, // or a function - the result is merged with // any `vite.config` file that is detected viteConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` ``` import { defineConfig } from 'cypress' import customViteConfig from './customConfig' export default defineConfig({ component: { devServer: { framework: 'react', bundler: 'vite', // optionally pass in vite config viteConfig: customViteConfig, // or a function - the result is merged with // any `vite.config` file that is detected viteConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` #### Sample React Vite Apps * [React Vite with TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/react-vite-ts) ### React with Webpack Cypress Component Testing works with React apps that use Webpack 5+ as the bundler. #### Required Packages Install these packages in your project to run React component tests with Webpack: | Package | Version | Purpose | | --- | --- | --- | | `cypress` | Latest | The Cypress App. Ships with the React mount adapter and the Webpack dev server | | `react` | 18 or 19 | The React library your components are built with | | `react-dom` | 18 or 19 | Renders your components into the DOM during a test | | `webpack` | 5 | Your app's bundler. Cypress compiles your specs with the Webpack config it detects in the project | You do not need to install `@cypress/react`, `@cypress/webpack-dev-server`, or `webpack-dev-server` separately. All three ship inside the `cypress` package, so `import { mount } from 'cypress/react'` and `bundler: 'webpack'` work as soon as Cypress is installed. #### Webpack Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') const webpackConfig = require('./webpack.config') module.exports = defineConfig({ component: { devServer: { framework: 'react', bundler: 'webpack', // optionally pass in webpack config webpackConfig, // or a function - the result is merged with any // webpack.config that is found webpackConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` ``` import { defineConfig } from 'cypress' import webpackConfig from './webpack.config' export default defineConfig({ component: { devServer: { framework: 'react', bundler: 'webpack', // optionally pass in webpack config webpackConfig, // or a function - the result is merged with any // webpack.config that is found webpackConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` If you don't provide a webpack config, Cypress will try to infer it. If Cypress cannot do so, or you want to make modifications to your config, you can specify it via the `webpackConfig` option. #### Sample React Webpack Apps * [React Webpack 5 with JavaScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/react-webpack5-js) ### Next.js Cypress Component Testing works with Next.js 15 and 16. #### Next.js Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'next', bundler: 'webpack', }, }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'next', bundler: 'webpack', }, }, }) ``` #### Next.js Caveats There are some specific caveats to consider when testing Next.js [Pages](https://nextjs.org/docs/basic-features/pages) in component testing. A page component could have additional logic in its `getServerSideProps` or `getStaticProps` methods. These methods only run on the server, so they are not available to run inside a component test. Trying to test a page in a component test would result in the props being passed into the page to be undefined. While you could pass in props directly to the page component in a component test, that would leave these server-side methods untested. However, an end-to-end test would execute and test a page entirely. Because of this, we recommend using E2E Testing over Component Testing for Next.js pages and Component Testing for individual components in a Next.js app. #### Sample Next.js Apps * [Next.js 15 with TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/react-next15-ts) * [Next.js 16 with TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/react-next16-ts) ## Community Resources * [Cypress Component Test Driven Design](https://muratkerem.gitbook.io/cctdd/) * [Cypress React Component Test Examples](https://github.com/muratkeremozcan/cypress-react-component-test-examples) ## See also * [Component Testing code coverage](/llm/markdown/app/tooling/code-coverage.md#component-testing-code-coverage) --- Source: https://docs.cypress.io/app/component-testing/styling-components.md Section: Cypress App # Styling Components Stylesheets are a critical part of your component's business logic. One of the best examples of this is a modal component. Common modal bugs include: z-index issues, inability to dismiss the overlay, and inability to interact with the parent page _after_ dismissing the modal. Node-based test runners like Jest or Vitest can't catch these kinds of issues because they render your styles in **emulated DOM environments** like JSDom. JSDom doesn't have a box model and certain kinds of assertions, such as if a parent is covering a child and preventing clicks, are not possible to test without a more realistic environment. On the other hand, browser-based runners like Cypress allow you to render your application's styles and components and allow Cypress's Driver to take advantage of the real box-model and style rendering engine. Cypress's commands like `cy.click` and assertions like `should('be.visible')` have business logic that makes sure the UI you're trying to assert on and interact with is visible and interactible for your end users. This is a benefit unique to browser-based test runners. ## Rendering Components Correctly The first time you mount _any_ new component, you may notice that the component doesn't look like it should. Unless your application is written _exclusively_ using Component-scoped CSS (e.g. Styled Components or Vue's Scoped Styles) you will need to follow this guide in order to get your component looking **and behaving** like it will in production. Ensure that whatever you're doing in production is happening within either the Component HTML file or the Component Support File. ## Component Support File When you load a component or end-to-end spec file, it will first load something called a supportFile. By default, this is created for you during first-time setup of Cypress Component Testing and is located at `cypress/support/component.js`. This file gives you the opportunity to set up your spec's environment. For component specs, you use this file to set up page-level concerns that would usually exist by the time you mount the component. Some examples include: 1. Run-time JavaScript code (state management, routers, UI libraries) 2. Global styles (style resets, Tailwind) As a rule, your Component Support File should look **very similar** to your application's main JavaScript (ie: main.js, index.js) and main CSS (ie: main.css, index.css) files. ## 3rd Party CSS Libraries (Tailwind, Bootstrap, PopperJS) Components can have three parts: markup, styles, and script logic. All three of these work together in order to deliver a working component. Styles are business logic, too. 1. Tailwind 2. CSS Modules 3. Scoped Styled 4. Styled Components 5. Regular Stylesheets 6. UI Libraries This guide will help you setup your test infrastructure to render your component's styles properly. Depending on how your application is built, the first time you mount a new component, it may be completely or somewhat unstyled. This makes sense. Many applications have some amount of one-time setup that is run outside of the component file. We build our applications within the context that they're supposed to run in, and we make assumptions that our components will always be rendered within a root-level component (such as an ``) or a top-level selector with style rules (such as `#app { /* styles in here */ }` ) When we attempt to isolate our component to put it under test, we need to put that environment back together. We'll go into that in a moment. First, let's talk about stylesheets, testing, and one of Cypress's biggest differences in contrast to other component testing tools. ## Importing Stylesheets Each application or component library imports styles a little differently. We'll go over a few methods and describe how you can quickly restructure your components to become more testable. If you do not follow this guide, your components will mount, but they won't look correct and you may not be able to benefit from some of the most valuable parts of Cypress. Namely, implicit checks for width, height, and overflow to ensure that your components not only exist in the page's HTML but are also visible. ## Rules for Setting Up Your Styles All of your application's styles need to end up in Cypress so that when your component mounts, it looks right. We expose two hooks for you to configure your styles: 1. An HTML file called `cypress/support/component-index.html` 2. A JavaScript support file called `cypress/support/component.js` When creating a production-like test environment, you should _always_ mimic your own application's setup. If your application has multiple `` tags to load fonts or other stylesheets within the `head`, ensure that the `cypress/support/component-index.html` file contains the same `` tags. The same logic follows for any styles loaded in your Application's `main.js` file. If you import a `./styles.css` at the top of your `main.js` file, make sure to `import` it in your `cypress/support/component.js` file. For this reason, it's strongly suggested to make a `src/setup.js` file that will be re-used in your `main.js` entrypoint as well as in your test setup. An example project structure would look like so: ``` > /cypress > /support > /component.js > /src > /main.js > /main.css > /setup.js ``` The contents of **setup.js** may look like so: ``` import '~normalize/normalize.css' import 'font-awesome' import './main.css' export const createStore = () => { return /* store */ } export const createRouter = () => { return /* router */ } export const createApp = () => { return } ``` and its usage in `main.js` could look like so: ``` import { createApp } from './setup.js' ReactDOM.render(createApp()) ``` and Cypress would re-use it in its support file ``` /* And that's it! */ import '../../src/setup.js' ``` The rest of this section is dedicated to discussing specific style problems you may have, including: Fonts, Icon Fonts, Style Resets, Global App Styles, and 3rd party component library styles. ### Global App Styles Your global application styles are usually in one of the following places: 1. A `styles.css` file you import within the `head` of your application. This should be loaded within your Cypress Index HTML file. 1. Within a root-level component like `App.jsx`, `App.vue`, `App.svelte`, etc. Decouple your Root CSS from your App or Entrypoint component by pulling out these global styles into a top-level stylesheet. Both Vue and Svelte embed global application styles into the main entry point components. The rest of your application expects to be rendered _within_ those components, and so any assumptions you made when writing those components must be replicated in your test environment or else your components won't look right. ``` ``` Should become ``` /* App.vue */ ``` 1. Your main stylesheet loads fonts ``` /* main.css */ @font-face { font-family: 'Fira Sans'; src: url('fonts/fira/eot/FiraSans-Regular.eot'); src: url('fonts/fira/eot/FiraSans-Regular.eot') format('embedded-opentype'), url('fonts/fira/woff2/FiraSans-Regular.woff2') format('woff2'), url('fonts/fira/woff/FiraSans-Regular.woff') format('woff'), url('fonts/fira/woff2/FiraSans-Regular.ttf') format('truetype'); font-weight: normal; font-style: normal; } ``` When fonts are loaded from local files like this, the files must be served by your component testing dev server so the browser can fetch them. Where you place the files and how you reference them depends on your bundler: * **Vite**: Put the font files in your project's [`public` directory](https://vite.dev/guide/assets#the-public-directory) (`public/` by default) and reference them with root-relative paths, for example `url('/fonts/fira/woff2/FiraSans-Regular.woff2')`. Cypress sets Vite's `base` to match its [`devServerPublicPathRoute`](/llm/markdown/app/component-testing/component-framework-configuration.md#devServerPublicPathRoute), and Vite rewrites these paths and serves the files accordingly. * **Webpack**: `webpack-dev-server` does not serve a static directory by default. Either `import` the font from a module so the bundler emits it, or add a [`static`](https://webpack.js.org/configuration/dev-server/#devserverstatic) entry to the `devServer` section of your webpack config so the files are served: ``` // webpack.config.js const path = require('path') module.exports = { devServer: { static: { directory: path.join(__dirname, 'public'), }, }, } ``` If your fonts are not loading, open the browser's network tab and confirm the request for the font file resolves rather than returning a 404. ### Icon Fonts: None of my icons are rendering ### Theme Providers: My components don't look right/compile because they can't access providers Theme Provider or other application-level wrappers like I18n or Material UI work by injecting themselves around your application. When you're component testing, you haven't rendered the component hierarchy surrounding your component. To solve issues like these, people review the Custom Commands and Wrappers To first explain why it's not right, you first have to explain what production-like even means. So we have this before & after up, and now our job is to step through the component under test and try to figure out where the differences between Production and Test are. Sometimes these are as simple as colors or fonts not lining up. Other times, the entire component or sections of it may not compile. The reason this doesn't look right is because: 1. My browser supports dark mode 2. The `` component provides its own styles --- Source: https://docs.cypress.io/app/component-testing/svelte/api.md Section: Cypress App # Svelte API The `cypress/svelte` module provides the following methods and interfaces for mounting and testing Svelte components. ## mount ``` import { mount } from 'cypress/svelte' ```
DescriptionMounts a Svelte component inside the Cypress browser
Signature

mount(Component: Component<Record<string, any>, Record<string, any>, any>, options?: MountOptions): Cypress.Chainable<MountReturn>

ReturnsCypress.Chainable<MountReturn>
mount Parameters
NameTypeDescription
component

Component<Record<string, any>, Record<string, any>, any>

Svelte component being mounted
optionsMountOptions (optional)options to customize the component being mounted
### Example ``` import Counter from './Counter.svelte' import { mount } from 'cypress/svelte' it('should render', () => { mount(Counter, { props: { count: 42 } }) cy.get('button').contains(42) }) ``` ## Interfaces ### MountOptions
members
NameTypeDescription
anchorElement (optional)
contextMap<any, any> (optional)
introboolean (optional)
logboolean (optional)
propsRecord<string any> (optional)
### MountReturn Type that the `mount` function yields
members
NameTypeDescription
componentRecord<string, any>
--- Source: https://docs.cypress.io/app/component-testing/svelte/examples.md Section: Cypress App # Svelte Examples To mount a component with `cy.mount()`, import the component and pass it to the method: ``` import { Stepper } from './stepper.svelte' it('mounts', () => { cy.mount(Stepper) }) ``` ## Passing Data to a Component You can pass props to a component by setting props in the options: `cy.mount()`: ``` it('mounts', () => { cy.mount(Stepper, { props: { count: 100 } }) }) ``` ## Testing Event Handlers To test emitted events from a Svelte component, we need to pass in a callback for when we increment the stepper. The Stepper component will need to invoke this callback for us. We can also pass in a Cypress spy so we can query the spy later for results. In the example below, we pass in the `onChange` callback handler and validate it was called as expected: ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(Stepper, { props: { onChange: onChangeSpy } }) cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` ## Accessing the Component Instance There might be times when you might want to access the component instance directly in your tests. To do so, use `.then()`, which enables us to work with the subject that was yielded from the `cy.mount()` command. ``` cy.mount(Stepper).then(({ component }) => { //component is the rendered instance of Stepper }) ``` ## Testing Error States `cy.mount()` is an asynchronous Cypress command: it _enqueues_ the mount and returns immediately, before the component ever renders. When a component throws during render, the error surfaces as an uncaught exception rather than as a synchronous throw. By default Cypress fails the test on any uncaught exception, so to assert on a render error you listen for it with [`cy.on('uncaught:exception')`](/llm/markdown/api/cypress-api/catalog-of-events.md#Uncaught-Exceptions) and return `false` to prevent Cypress from failing the test: ``` import UserProfile from './UserProfile.svelte' it('surfaces a render error', () => { cy.on('uncaught:exception', (err) => { // Assert on the error thrown during render... expect(err.message).to.include('user is required') // ...and return false so Cypress does not fail the test. return false }) cy.mount(UserProfile) }) ``` ### Testing with `` The recommended way to render fallback UI in Svelte 5 is [``](https://svelte.dev/docs/svelte/svelte-boundary) with a `failed` snippet. Rather than nesting components through `cy.mount()`, the cleanest approach is a dedicated fixture component that imports the failing component directly, then mount that fixture and assert the fallback is shown: ``` // ErrorBoundary.cy.js import ErrorBoundary from './ErrorBoundary.svelte' it('displays the fallback UI on error', () => { // renders fallback UI, but it does NOT stop the error from // propagating to Cypress as an uncaught exception. Cypress fails on uncaught // exceptions by default, so we must still suppress that behavior. cy.on('uncaught:exception', (err) => { // Only suppress the specific error we expect. expect(err.message).to.include('I crashed!') return false }) cy.mount(ErrorBoundary) cy.get('[data-cy=fallback]').should('contain', 'Something went wrong.') }) ``` Where the `ErrorBoundary` fixture wraps the failing component in a `` with a `failed` snippet: ``` {#snippet failed(error)}
Something went wrong.
{/snippet}
``` --- Source: https://docs.cypress.io/app/component-testing/svelte/overview.md Section: Cypress App # Svelte Component Testing Cypress Component Testing supports Svelte 5 in a variety of different frameworks: * [Svelte with Vite](#Svelte-with-Vite) * [Svelte with Webpack](#Svelte-with-Webpack) Svelte is currently in alpha support for component testing. ## Tutorial Visit the [Getting Started Guide](/llm/markdown/app/component-testing/get-started.md) for a step-by-step tutorial on adding component testing to any project and how to write your first tests. ## Installation To get up and running with Cypress Component Testing in Svelte, install Cypress into your project: * npm * Yarn * pnpm * Bun ``` npm install cypress --save-dev ``` ``` yarn add cypress --dev ``` ``` pnpm add --save-dev cypress ``` ``` bun add --dev cypress ``` Open Cypress: * npm * Yarn * pnpm * Bun ``` npx cypress open ``` ``` yarn cypress open ``` ``` pnpm cypress open ``` ``` bunx cypress open ``` Choose Component Testing The Cypress Launchpad will guide you through configuring your project. For a step-by-step guide on how to create a component test, refer to the [Getting Started](/llm/markdown/app/component-testing/get-started.md) guide. For usage and examples, visit the [Svelte Examples](/llm/markdown/app/component-testing/svelte/examples.md) guide. ## Framework Configuration Cypress Component Testing works out of the box with [Vite](https://vitejs.dev/), and a custom [Webpack](https://webpack.js.org/) config. Cypress will automatically detect one of these frameworks during setup and configure them properly. For a full explanation of how the dev server and bundler work — including automatic config detection and override options — see [Component Testing Configuration — Dev Server and Bundler](/llm/markdown/app/component-testing/component-framework-configuration.md#Dev-Server-and-Bundler). The examples below are for quick reference. ### Svelte with Vite Cypress Component Testing works with Svelte apps that use Vite `8.x` as the bundler. #### Vite Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'svelte', bundler: 'vite', }, }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'svelte', bundler: 'vite', }, }, }) ``` #### Svelte Vite Sample Apps * [Svelte 5 with Vite and TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/svelte-vite-ts) ### Svelte with Webpack Cypress Component Testing works with Svelte apps that use Webpack 5+ as the bundler. #### Webpack Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') const webpackConfig = require('./webpack.config') module.exports = defineConfig({ component: { devServer: { framework: 'svelte', bundler: 'webpack', // optionally pass in webpack config webpackConfig, }, }, }) ``` ``` import { defineConfig } from 'cypress' import webpackConfig from './webpack.config' export default defineConfig({ component: { devServer: { framework: 'svelte', bundler: 'webpack', // optionally pass in webpack config webpackConfig, }, }, }) ``` If you don't provide one, Cypress will try to infer your webpack config. If Cypress cannot or you want to make modifications to your config, you can pass it in manually via the `webpackConfig` option. #### Svelte Webpack Sample Apps * [Svelte 5 Webpack 5 with Typescript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/svelte-webpack-ts) ## See also * [Component Testing code coverage](/llm/markdown/app/tooling/code-coverage.md#component-testing-code-coverage) --- Source: https://docs.cypress.io/app/component-testing/vue/api.md Section: Cypress App # Vue API The `cypress/vue` module provides the following methods and interfaces for mounting and testing Vue components. ## mount ``` import { mount } from 'cypress/vue' ```
Description

Used for mounting Vue components in isolation. It is responsible for rendering the component within Cypress's sandboxed iframe and handling any framework-specific cleanup.

Signature

mount(originalComponent: { new (...args: any[]): ; __vccOpts: any; }, options?: MountOptions): Cypress.Chainable<MountReturn>

mount Parameters
NameTypeDescription
originalComponentnew (...args: any[])The component to mount in test
optionsMountOptions (optional)The options for mounting the component
## Interfaces ### MountOptions ([Vue 3 MountingOptions](https://test-utils.vuejs.org/api/#mount) or [Vue Test Utils](https://test-utils.vuejs.org/)) ### MountReturn Type that the `mount` function yields
members
NameTypeDescription
wrapperVueWrapperThe Vue Test Utils wrapper
componentVueComponentThe component instance
--- Source: https://docs.cypress.io/app/component-testing/vue/examples.md Section: Cypress App # Vue Examples To mount a component with `cy.mount()`, import the component and pass it to the method: ``` import { Stepper } from './Stepper.vue' it('mounts', () => { cy.mount(Stepper) }) ``` ## Passing Data to a Component You can pass props and events to a component by setting `props` in the options: ``` cy.mount(Stepper, { props: { initial: 100, }, }) ``` ## Testing Event Handlers Pass a Cypress [spy](/llm/markdown/app/guides/stubs-spies-and-clocks.md#Spies) to an event prop and validate it was called: ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(Stepper, { props: { onChange: onChangeSpy } }) cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 1) }) ``` ## Using JSX The mount command also supports JSX syntax (provided that you've configured your bundler to support transpiling JSX or TSX files). Some might find using JSX syntax beneficial when writing tests. Sample with JSX: ``` it('clicking + fires a change event with the incremented value', () => { const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount() cy.get('[data-cy=increment]').click() cy.get('@onChangeSpy').should('have.been.calledWith', 101) }) ``` ## Testing Error States `cy.mount()` is an asynchronous Cypress command: it _enqueues_ the mount and returns immediately, before the component ever renders. When a component throws during render, the error surfaces as an uncaught exception rather than as a synchronous throw. By default Cypress fails the test on any uncaught exception, so to assert on a render error you listen for it with [`cy.on('uncaught:exception')`](/llm/markdown/api/cypress-api/catalog-of-events.md#Uncaught-Exceptions) and return `false` to prevent Cypress from failing the test: ``` import UserProfile from './UserProfile.vue' it('surfaces a render error', () => { cy.on('uncaught:exception', (err) => { // Assert on the error thrown during render... expect(err.message).to.include('user is required') // ...and return false so Cypress does not fail the test. return false }) cy.mount(UserProfile) }) ``` ### Testing with `onErrorCaptured` The recommended way to render fallback UI in Vue is a wrapper component that uses the [`onErrorCaptured`](https://vuejs.org/api/composition-api-lifecycle#onerrorcaptured) lifecycle hook to catch errors from its descendants. You can mount a component inside such a wrapper and assert that the fallback UI is displayed: ``` // ErrorBoundary.cy.js import { h } from 'vue' import ErrorBoundary from './ErrorBoundary.vue' import ChildWithError from './ChildWithError.vue' it('displays the fallback UI on error', () => { // onErrorCaptured renders fallback UI, but it does NOT stop the error from // propagating to Cypress as an uncaught exception. Cypress fails on uncaught // exceptions by default, so we must still suppress that behavior. cy.on('uncaught:exception', (err) => { // Only suppress the specific error we expect. expect(err.message).to.include('I crashed!') return false }) cy.mount(() => h(ErrorBoundary, () => h(ChildWithError))) cy.get('[data-cy=fallback]').should('contain', 'Something went wrong.') }) ``` Where `ErrorBoundary` captures the error with `onErrorCaptured` and renders fallback UI: ``` ``` ## Using Slots ### Default Slot * DefaultSlot.cy.js * DefaultSlot.cy.jsx (JSX) * DefaultSlot.vue ``` import DefaultSlot from './DefaultSlot.vue' describe('', () => { it('renders', () => { cy.mount(DefaultSlot, { slots: { default: 'Hello there!', }, }) cy.get('div.content').should('have.text', 'Hello there!') }) }) ``` ``` import DefaultSlot from './DefaultSlot.vue' describe('', () => { it('renders', () => { cy.mount(Hello there!) cy.get('div.content').should('have.text', 'Hello there!') }) }) ``` ``` ``` ### Named Slot * NamedSlot.cy.js * NamedSlot.cy.jsx (JSX) * NamedSlot.vue ``` import NamedSlot from './NamedSlot.vue' describe('', () => { it('renders', () => { const slots = { header: 'my header', footer: 'my footer', } cy.mount(NamedSlot, { slots, }) cy.get('header').should('have.text', 'my header') cy.get('footer').should('have.text', 'my footer') }) }) ``` ``` import NamedSlot from './NamedSlot.vue' describe('', () => { it('renders', () => { const slots = { header: 'my header', footer: 'my footer', } cy.mount({{ ...slots }}) cy.get('header').should('have.text', 'my header') cy.get('footer').should('have.text', 'my footer') }) }) ``` ``` ``` For more info on testing Vue components with slots, refer to the [Vue Test Utils Slots guide](https://test-utils.vuejs.org/guide/advanced/slots.html). ## Using Vue Test Utils In order to encourage interoperability between your existing component tests and Cypress, we support using Vue Test Utils' API. ``` cy.mount(Stepper).then(({ wrapper, component }) => { // `wrapper` is the Vue Test Utils wrapper // `component` is the component instance itself }) ``` If you intend to use the `wrapper` frequently and use Vue Test Util's API, we recommend you write a [custom mount command](/llm/markdown/api/commands/mount.md) and create a Cypress alias to get back at the `wrapper`. * cypress/support/component.js * cypress/support/component.ts ``` import { mount } from 'cypress/vue' Cypress.Commands.add('mount', (...args) => { return mount(...args).then(({ wrapper }) => { return cy.wrap(wrapper).as('vue') }) }) ``` ``` import { mount } from 'cypress/vue' type MountParams = Parameters declare global { namespace Cypress { interface Chainable { /** * Mounts a Vue component and aliases the Vue Test Utils wrapper as `@vue` * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount(...args: MountParams): Chainable } } } Cypress.Commands.add('mount', (...args) => { return mount(...args).then(({ wrapper }) => { return cy.wrap(wrapper).as('vue') }) }) ``` ``` // the "@vue" alias will now work anywhere // after you've mounted your component cy.mount(Stepper).doStuff().get('@vue') // The subject is now the Vue Wrapper ``` This means that you are able to get to the resulting `wrapper` returned from the `mount` command and use `wrapper.emitted()` in order to gain access to Native DOM events that were fired, as well as custom events that were emitted by your component under test. Because `wrapper.emitted()` is only data, and NOT spy-based you will have to unpack its results to write assertions. Your test failure messages will not be as helpful because you're not able to use the Sinon-Chai library that Cypress ships, which comes with methods such as `to.have.been.called` and `to.have.been.calledWith`. Usage of the `cy.get('@vue')` alias may look something like the below code snippet. Notice that we're using the `'should'` function signature in order to take advantage of Cypress's [retryability](/llm/markdown/app/guides/test-retries.md). If we chained using `cy.then` instead of `cy.should`, we may run into the kinds of issues you have in Vue Test Utils tests where you have to use `await` frequently in order to make sure the DOM has updated or any reactive events have fired. * With emitted * With spies ``` cy.mount(Stepper, { props: { initial: 100 } }) cy.get(incrementSelector).click() cy.get('@vue').should(({ wrapper }) => { expect(wrapper.emitted('change')).to.have.length expect(wrapper.emitted('change')[0][0]).to.equal('101') }) ``` ``` const onChangeSpy = cy.spy().as('onChangeSpy') cy.mount(Stepper, { props: { initial: 100, onChange: onChangeSpy } }) cy.get(incrementSelector).click() cy.get('@onChangeSpy').should('have.been.calledWith', '101') ``` Regardless of our recommendation to use spies instead of the internal Vue Test Utils API, you may decide to continue using `emitted` as it _automatically_ records every single event emitted from the component, and so you won't have to create a spy for every event emitted. This auto-spying behavior could be useful for components that emit _many_ custom events. ## Custom Mount Commands ### Customizing `cy.mount()` While you can use the [mount()](/llm/markdown/app/component-testing/vue/api.md#mount) function in your tests, we recommend using [`cy.mount()`](/llm/markdown/api/commands/mount.md), which is a [custom command](/llm/markdown/api/cypress-api/custom-commands.md) that is defined in the **cypress/support/component.js** file: * cypress/support/component.js * cypress/support/component.ts ``` import { mount } from 'cypress/vue' Cypress.Commands.add('mount', mount) ``` ``` import { mount } from 'cypress/vue' declare global { namespace Cypress { interface Chainable { mount: typeof mount } } } Cypress.Commands.add('mount', mount) ``` This allows you to use `cy.mount()` in any test without having to import the `mount()` function in each and every spec file. By default, `cy.mount()` is a simple passthrough to `mount()`, however, you can customize `cy.mount()` to fit your needs. For instance, if you are using plugins or other global app-level setups in your Vue app, you can configure them here. Below are a few examples that demonstrate using a custom mount command. These examples can be adjusted for most other providers that you will need to support. ### Replicating Plugins Most applications will have state management or routing. Both of these are Vue plugins. * cypress/support/component.js * With JSX * cypress/support/component.ts ``` import { createPinia } from 'pinia' // or Vuex import { createI18n } from 'vue-i18n' import { mount } from 'cypress/vue' import { h } from 'vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const i18nOptions = { locale: 'en', messages: { en: { hello: 'hello!', }, ja: { hello: 'こんにちは!', }, }, } Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(createPinia()) args.global.plugins.push(createI18n()) return mount( () => { return h(VApp, {}, component) }, ...args ) }) ``` ``` import { createPinia } from 'pinia' // or Vuex import { createI18n } from 'vue-i18n' import { mount } from 'cypress/vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const i18nOptions = { locale: 'en', messages: { en: { hello: 'hello!', }, ja: { hello: 'こんにちは!', }, }, } Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(createPinia()) args.global.plugins.push(createI18n()) // is a built-in component that comes with Vue return mount( () => ( ), ...args ) }) ``` ``` import { createPinia } from 'pinia' // or Vuex import { createI18n } from 'vue-i18n' import { mount } from 'cypress/vue' import { h } from 'vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const i18nOptions = { locale: 'en', messages: { en: { hello: 'hello!', }, ja: { hello: 'こんにちは!', }, }, } type MountParams = Parameters declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Vue Components * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount(...args: MountParams): Chainable } } } Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(createPinia()) args.global.plugins.push(createI18n()) return mount( () => { return h(VApp, {}, component) }, ...args ) }) ``` ### Replicating the expected Component Hierarchy Some Vue applications, most famously Vue apps built on top of Vuetify, require certain components to be structured in a specific hierarchy. All Vuetify applications require that you wrap your app in a `VApp` component when you build it. This is an implementation detail of Vuetify, but once users try to test components that depend on Vuetify, they get Vuetify-specific compilation errors and quickly find out that **they need to replicate that component hierarchy any time they need to mount a component that uses a Vuetify component**! Custom `cy.mount` commands to the rescue! You may find the JSX syntax to be more straightforward. You'll also need to replicate the plugin setup steps from the Vuetify docs for everything to compile. * cypress/support/component.js * With JSX * cypress/support/component.ts ``` import Vuetify from 'vuetify/lib' import { VApp } from 'vuetify' import { mount } from 'cypress/vue' import { h } from 'vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const vuetifyOptions = {} Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(new Vuetify(vuetifyOptions)) return mount( () => { return h(VApp, {}, component) }, ...args ) }) ``` ``` import Vuetify from 'vuetify/lib' import { VApp } from 'vuetify' import { mount } from 'cypress/vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const vuetifyOptions = {} Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(new Vuetify(vuetifyOptions)) // is a built-in component that comes with Vue return mount( () => ( ), ...args ) }) ``` ``` import Vuetify from 'vuetify/lib' import { VApp } from 'vuetify' import { mount } from 'cypress/vue' import { h } from 'vue' // We recommend that you pull this out // into a constants file that you share with // your main.js file. const vuetifyOptions = {} type MountParams = Parameters declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Vue Components * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount(...args: MountParams): Chainable } } } Cypress.Commands.add('mount', (component, ...args) => { args.global = args.global || {} args.global.plugins = args.global.plugins || [] args.global.plugins.push(new Vuetify(vuetifyOptions)) return mount( () => { return h(VApp, {}, component) }, ...args ) }) ``` ### Vue Router To use Vue Router, create a command to register the plugin and pass in a custom implementation of the router via the options param. * cypress/support/component.js * cypress/support/component.ts * Spec Usage ``` import { mount } from 'cypress/vue' import { createMemoryHistory, createRouter } from 'vue-router' import { routes } from '../../src/router' Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.plugins = options.global.plugins || [] // create router if one is not provided if (!options.router) { options.router = createRouter({ routes: routes, history: createMemoryHistory(), }) } // Add router plugin options.global.plugins.push({ install(app) { app.use(options.router) }, }) return mount(component, options) }) ``` ``` import { mount } from 'cypress/vue' import { createMemoryHistory, createRouter, Router } from 'vue-router' import { routes } from '../../src/router' type MountParams = Parameters type OptionsParam = MountParams[1] & { router?: Router } declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Vue Components * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount(component: any, options?: OptionsParam): Chainable } } } Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.plugins = options.global.plugins || [] // create router if one is not provided if (!options.router) { options.router = createRouter({ routes: routes, history: createMemoryHistory(), }) } // Add router plugin options.global.plugins.push({ install(app) { app.use(options.router) }, }) return mount(component, options) }) ``` ``` import Navigation from './Navigation.vue' import { routes } from '../router' import { createMemoryHistory, createRouter } from 'vue-router' it('home link should be active when url is "/"', () => { // No need to pass in custom router as default url is '/' cy.mount() cy.get('a').contains('Home').should('have.class', 'router-link-active') }) it('login link should be active when url is "/login"', () => { // Create a new router instance for each test const router = createRouter({ routes: routes, history: createMemoryHistory(), }) // Change location to `/login`, // and await on the promise with cy.wrap cy.wrap(router.push('/login')) // Pass the already initialized router for use cy.mount(, { router }) cy.get('a').contains('Login').should('have.class', 'router-link-active') }) ``` Calling `router.push()` in the router for Vue 3 is an asynchronous operation. Use the [cy.wrap](/llm/markdown/api/commands/wrap.md) command to have Cypress await the promise's resolve before it continues with other commands: ### Vuex To use a component that uses [Vuex](https://vuex.vuejs.org/), create a `mount` command that configures a Vuex store for your component. * cypress/support/component.js * cypress/support/component.ts * Spec Usage ``` import { mount } from 'cypress/vue' import { getStore } from '../../src/plugins/store' Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.stubs = options.global.stubs || {} options.global.stubs['transition'] = false options.global.components = options.global.components || {} options.global.plugins = options.global.plugins || [] // Use store passed in from options, or initialize a new one const { store = getStore(), ...mountOptions } = options // Add Vuex plugin options.global.plugins.push({ install(app) { app.use(store) }, }) return mount(component, mountOptions) }) ``` The `getStore` method is a factory method that initializes Vuex and creates a new store. It is important that the store be initialized with each new test to ensure changes to the store don't affect other tests. ``` import { mount } from 'cypress/vue' import { getStore } from '../../src/plugins/store' import { Store } from 'vuex' type MountParams = Parameters type OptionsParam = MountParams[1] declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Vue Components * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount( component: any, options?: OptionsParam & { store?: Store } ): Chainable } } } Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.stubs = options.global.stubs || {} options.global.stubs['transition'] = false options.global.components = options.global.components || {} options.global.plugins = options.global.plugins || [] // Use store passed in from options, or initialize a new one const { store = getStore(), ...mountOptions } = options // Add Vuex plugin options.global.plugins.push({ install(app) { app.use(store) }, }) return mount(component, mountOptions) }) ``` ``` import { getStore } from '@/plugins/store' import UserProfile from './UserProfile.vue' it.only('User profile should display user name', () => { const user = { name: 'test person' } // getStore is a factory method that creates a new store const store = getStore() // mutate the store with user store.commit('setUser', user) cy.mount(UserProfile, { store, }) cy.get('div.name').should('have.text', user.name) }) ``` ### Global Components If you have components that are registered globally in the main application file, set them up in your mount command so your component will render them properly: * cypress/support/component.js * cypress/support/component.ts ``` import { mount } from 'cypress/vue' import Button from '../../src/components/Button.vue' Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.components = options.global.components || {} // Register global components options.global.components['Button'] = Button return mount(component, options) }) ``` ``` import { mount } from 'cypress/vue' import Button from '../../src/components/Button.vue' type MountParams = Parameters type OptionsParam = MountParams[1] declare global { namespace Cypress { interface Chainable { /** * Helper mount function for Vue Components * @param component Vue Component or JSX Element to mount * @param options Options passed to Vue Test Utils */ mount(component: any, options?: OptionsParam): Chainable } } } Cypress.Commands.add('mount', (component, options = {}) => { // Setup options object options.global = options.global || {} options.global.components = options.global.components || {} // Register global components options.global.components['Button'] = Button return mount(component, options) }) ``` --- Source: https://docs.cypress.io/app/component-testing/vue/overview.md Section: Cypress App # Vue Component Testing Cypress Component Testing supports Vue 3+ with the following frameworks: * [Vue with Vite](#Vue-with-Vite) * [Vue with Webpack](#Vue-with-Webpack) ## Tutorial Visit the [Getting Started Guide](/llm/markdown/app/component-testing/get-started.md) for a step-by-step tutorial on adding component testing to any project and how to write your first tests. ## Installation To get up and running with Cypress Component Testing in Vue, install Cypress into your project: * npm * Yarn * pnpm * Bun ``` npm install cypress --save-dev ``` ``` yarn add cypress --dev ``` ``` pnpm add --save-dev cypress ``` ``` bun add --dev cypress ``` Open Cypress: * npm * Yarn * pnpm * Bun ``` npx cypress open ``` ``` yarn cypress open ``` ``` pnpm cypress open ``` ``` bunx cypress open ``` Choose Component Testing The Cypress Launchpad will guide you through configuring your project. For a step-by-step guide on how to create a component test, refer to the [Getting Started](/llm/markdown/app/component-testing/get-started.md) guide. For usage and examples, visit the [Vue Examples](/llm/markdown/app/component-testing/vue/examples.md) guide. ## Framework Configuration Cypress Component Testing works out of the box with [Vite](https://vitejs.dev/), and a custom [Webpack](https://webpack.js.org/) config. Cypress will automatically detect one of these frameworks during setup and configure them properly. For a full explanation of how the dev server and bundler work — including automatic config detection and override options — see [Component Testing Configuration — Dev Server and Bundler](/llm/markdown/app/component-testing/component-framework-configuration.md#Dev-Server-and-Bundler). The examples below are for quick reference. ### Vue with Vite Cypress Component Testing works with Vue apps that use Vite `8.x` as the bundler. #### Vite Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }) ``` #### Vue Vite Sample Apps * [Vue 3 Vite with TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/vue3-vite-ts) ### Vue with Webpack Cypress Component Testing works with Vue apps that use Webpack 5+ as the bundler. #### Webpack Configuration * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') const webpackConfig = require('./webpack.config') module.exports = defineConfig({ component: { devServer: { framework: 'vue', bundler: 'webpack', // optionally pass in webpack config webpackConfig, webpackConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` ``` import { defineConfig } from 'cypress' import webpackConfig from './webpack.config' export default defineConfig({ component: { devServer: { framework: 'vue', bundler: 'webpack', // optionally pass in webpack config webpackConfig, webpackConfig: async () => { // ... do things ... const modifiedConfig = await injectCustomConfig(baseConfig) return modifiedConfig }, }, }, }) ``` If you don't provide one, Cypress will try to infer your webpack config. If Cypress cannot or you want to make modifications to your config, you can pass it in manually via the `webpackConfig` option. #### Vue Webpack Sample Apps * [Vue 3 Webpack 5 with TypeScript](https://github.com/cypress-io/cypress-component-testing-apps/tree/main/vue3-webpack-ts) ## Using Cypress with Nuxt Cypress does not ship a dedicated [Nuxt](https://nuxt.com/) framework definition, and it does not read `nuxt.config`. Instead, you component test a Nuxt 3+ app the same way you test any Vue 3 + Vite project, by setting the `vue` framework and the `vite` bundler: * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ component: { devServer: { framework: 'vue', bundler: 'vite', }, }, }) ``` Because Cypress does not execute `nuxt.config`, the bundler behavior Nuxt normally provides is not applied for you. Account for the following in the config you pass to Cypress: * **Path aliases** — Nuxt's `~` and `@` aliases are not visible to Cypress. Declare the ones your components use via `viteConfig`. See [Meta-frameworks that own the bundler config](/llm/markdown/app/component-testing/component-framework-configuration.md#Meta-frameworks-that-own-the-bundler-config). * **Auto-imports** — Nuxt auto-imports components and composables (such as `ref`, `useRoute`, and `useRuntimeConfig`). These are not available when a component is mounted in isolation, so either import what your component relies on explicitly or add the equivalent Vite plugin to your `viteConfig`. ## See also * [Component Testing code coverage](/llm/markdown/app/tooling/code-coverage.md#component-testing-code-coverage) --- Source: https://docs.cypress.io/app/continuous-integration/aws-codebuild.md Section: Cypress App # Run Cypress in AWS CodeBuild The example below is a basic CI setup and job using the [AWS CodeBuild](https://aws.amazon.com/codebuild/) to run Cypress tests. This AWS CodeBuild configuration is placed within `buildspec.yml`. Detailed documentation is available in the [AWS CodeBuild Documentation](https://docs.aws.amazon.com/codebuild/). buildspec.yml ``` version: 0.2 phases: install: runtime-versions: nodejs: latest commands: # Set COMMIT_INFO variables to send Git specifics to Cypress Cloud when recording # https://docs.cypress.io/app/continuous-integration/overview#Git-information - export COMMIT_INFO_BRANCH="$(git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g')" - export COMMIT_INFO_MESSAGE="$(git log -1 --pretty=%B)" - export COMMIT_INFO_EMAIL="$(git log -1 --pretty=%ae)" - export COMMIT_INFO_AUTHOR="$(git log -1 --pretty=%an)" - export COMMIT_INFO_SHA="$(git log -1 --pretty=%H)" - export COMMIT_INFO_REMOTE="$(git config --get remote.origin.url)" - npm ci pre_build: commands: - npm run cy:verify - npm run cy:info build: commands: - npm run start:ci & - npx cypress run --record ``` **How this buildspec works:** * On _push_ to this repository, this job will provision and start AWS-hosted Amazon Linux instance with Node.js for running the outlined `pre_build` and `build` for the declared commands within the `commands` section of the configuration. * [AWS CodeBuild](https://aws.amazon.com/codebuild/) will checkout our code from our GitHub repository. * Finally, our `buildspec.yml` configuration will: * Install npm dependencies * Start the project web server (`npm start:ci`) * Run the Cypress tests within our GitHub repository. To run in an installed browser such as Chrome or Firefox, use a Cypress Docker image that includes browsers — see [Testing with Cypress Docker Images](#Testing-with-Cypress-Docker-Images) below. **Try it out** To try out the example above yourself, fork the [Cypress Kitchen Sink](https://github.com/cypress-io/cypress-example-kitchensink) example project and place the above [AWS CodeBuild](https://aws.amazon.com/codebuild/) configuration in `buildspec.yml`. ## Testing with Cypress Docker Images As of version 0.2, CodeBuild does not provide a way to specify a custom image for single build configurations. One way to solve this is using an [AWS CodeBuild batch build-list strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-list). ### Enabling batch builds Per the [AWS CodeBuild batch build documentation](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html), AWS CodeBuild creates a separate build for each possible configuration combination for a [batch build-list strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-list). Therefore, AWS CodeBuild projects must be created or updated to run batch configuration. Follow these steps to enable batch configuration for existing AWS CodeBuild projects: * Navigate to the AWS CodeBuild Console * Select the project * Click "Edit" **\-->** "Batch Configuration" * Create "New service Role" and enter the name of the role * Leave all other options as optional * Click "Update batch configuration" * Start the Build ### Cypress Amazon Public ECR AWS CodeBuild offers a [build-list strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-list) of different job configurations for a single job definition. The [build-list strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-list) offers a way to specify an image hosted on [Docker Hub](https://hub.docker.com/) or the [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/). [Docker Images](https://github.com/cypress-io/cypress-docker-images) for running Cypress locally and in CI are published to the [Amazon ECR Public Gallery](https://gallery.ecr.aws).: * [Cypress 'base' Amazon ECR Public Gallery](https://gallery.ecr.aws/cypress-io/cypress/base) * [Cypress 'browsers' Amazon ECR Public Gallery](https://gallery.ecr.aws/cypress-io/cypress/browsers) * [Cypress 'included' Amazon ECR Public Gallery](https://gallery.ecr.aws/cypress-io/cypress/included) * [Cypress 'factory' Amazon ECR Public Gallery](https://gallery.ecr.aws/cypress-io/cypress/factory) Read about [Cypress docker variants](/llm/markdown/app/continuous-integration/overview.md#Cypress-Docker-variants) to decide which image is best for your project. buildspec.yml ``` version: 0.2 ## AWS CodeBuild Batch configuration ## https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html ## Define build to run using the ## "cypress/browsers:22.15.0" image ## from the Cypress Amazon ECR Public Gallery batch: fast-fail: false build-list: - identifier: cypress-e2e-tests env: image: public.ecr.aws/cypress-io/cypress/browsers:22.15.0 phases: install: runtime-versions: nodejs: latest commands: # Set COMMIT_INFO variables to send Git specifics to Cypress Cloud when recording # https://docs.cypress.io/app/continuous-integration/overview#Git-information - export COMMIT_INFO_BRANCH="$(git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g')" - export COMMIT_INFO_MESSAGE="$(git log -1 --pretty=%B)" - export COMMIT_INFO_EMAIL="$(git log -1 --pretty=%ae)" - export COMMIT_INFO_AUTHOR="$(git log -1 --pretty=%an)" - export COMMIT_INFO_SHA="$(git log -1 --pretty=%H)" - export COMMIT_INFO_REMOTE="$(git config --get remote.origin.url)" - npm ci pre_build: commands: - npm run cy:verify - npm run cy:info build: commands: - npm run start:ci & - npx cypress run --record --browser firefox ``` ## Caching Dependencies and Build Artifacts Caching with [AWS CodeBuild](https://aws.amazon.com/codebuild/) directly can be challenging. The [Build caching in AWS CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/build-caching.html) document offers details on local or Amazon S3 caching. Per the documentation, "Local caching stores a cache locally on a build host that is available to that build host only". This will not be useful during parallel test runs. The "Amazon S3 caching stores the cache in an Amazon S3 bucket that is available across multiple build hosts". While this may sound useful, in practice the upload of cached dependencies can take some time. Furthermore, each worker will attempt to save it's dependency cache to Amazon S3, which increases build time significantly. Beyond the scope of this guide, but [AWS CodePipeline](https://aws.amazon.com/codepipeline) may be of use to cache the initial source, dependencies and build output for use in AWS CodeBuild jobs using [AWS CodePipeline Input and Output Artifacts](https://docs.aws.amazon.com/codepipeline/latest/userguide/welcome-introducing-artifacts.html). Reference the [AWS CodePipeline integration with CodeBuild and multiple input sources and output artifacts sample](https://docs.aws.amazon.com/codebuild/latest/userguide/sample-pipeline-multi-input-output.html) example for details on how to configure a CodePipeline with an output artifact. ## Parallelization [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md) offers the ability to [parallelize and group test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) along with additional insights and [analytics](/llm/markdown/cloud/features/analytics/overview.md) for Cypress tests. AWS CodeBuild offers a [batch build-matrix strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-matrix) for declaring different job configurations for a single job definition. The [batch build-matrix strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-matrix) provides an option to specify a container image for the job. Jobs declared within a build-matrix strategy can run in parallel which enables us run multiples instances of Cypress at same time as we will see later in this section. See [Enabling batch builds](#Enabling-batch-builds) for instructions on how to enable batch builds. The following configuration using the `--parallel` and `--record` flags to [cypress run](/llm/markdown//app//app/command-line.md#cypress-run) requires setting up recording test results to [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md). ### Parallelizing the build To setup multiple containers to run in parallel, the `build-matrix` configuration uses a set of variables (`CY_GROUP_SPEC` and `WORKERS`) with a list of items specific to each group for the build. The fields are delimited by a pipe (`|`) character as follows: ``` ## Group Name | Browser | Specs | Cypress Configuration options (optional) 'UI-Chrome-Mobile|chrome|cypress/tests/ui/*|viewportWidth=375,viewportHeight=667' ``` The `build-matrix` will run all permutations delimited items. buildspec.yml ``` batch: fast-fail: false build-matrix: # ... dynamic: env: # ... variables: CY_GROUP_SPEC: - 'UI-Chrome|chrome|cypress/tests/ui/*' - 'UI-Chrome-Mobile|chrome|cypress/tests/ui/*|viewportWidth=375,viewportHeight=667' - 'API|chrome|cypress/tests/api/*' - 'UI-Firefox|firefox|cypress/tests/ui/*' - 'UI-Firefox-Mobile|firefox|cypress/tests/ui/*|viewportWidth=375,viewportHeight=667' ``` During the install phase, we utilize shell scripting with the [cut command](https://en.wikipedia.org/wiki/Cut_\(Unix\)) to assign values from the delimited `CY_GROUP_SPEC` passed to the worker into shell variables that will be used in the `build` phase when running `cypress run`. buildspec.yml ``` batch: # ... phases: install: commands: # Set COMMIT_INFO variables to send Git specifics to Cypress Cloud when recording # https://docs.cypress.io//app/continuous-integration/overview#Git-information - export COMMIT_INFO_BRANCH="$(git rev-parse HEAD | xargs git name-rev | cut -d' ' -f2 | sed 's/remotes\/origin\///g')" - export COMMIT_INFO_MESSAGE="$(git log -1 --pretty=%B)" - export COMMIT_INFO_EMAIL="$(git log -1 --pretty=%ae)" - export COMMIT_INFO_AUTHOR="$(git log -1 --pretty=%an)" - export COMMIT_INFO_SHA="$(git log -1 --pretty=%H)" - export COMMIT_INFO_REMOTE="$(git config --get remote.origin.url)" - CY_GROUP=$(echo $CY_GROUP_SPEC | cut -d'|' -f1) - CY_BROWSER=$(echo $CY_GROUP_SPEC | cut -d'|' -f2) - CY_SPEC=$(echo $CY_GROUP_SPEC | cut -d'|' -f3) - CY_CONFIG=$(echo $CY_GROUP_SPEC | cut -d'|' -f4) - npm ci ## ... ``` To parallelize the runs, we need to add an additional variable to the [build-matrix strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-matrix), `WORKERS`. buildspec.yml ``` batch: fast-fail: false build-matrix: # ... dynamic: env: # ... variables: CY_GROUP_SPEC: # ... WORKERS: - 1 - 2 - 3 - 4 - 5 ``` **Note** The `WORKERS` array is filled with filler (or _dummy_) items to provision the desired number of CI machine instances within the [batch build-matrix strategy](https://docs.aws.amazon.com/codebuild/latest/userguide/batch-build-buildspec.html#build-spec.batch.build-matrix) and will provide 5 workers to each group defined in the `CY_GROUP_SPEC`. Finally, the script variables are passed to the call to `cypress run`. buildspec.yml ``` phases: install: # ... build: commands: - npm start:ci & - npx cypress run --record --parallel --browser $CY_BROWSER --ci-build-id $CODEBUILD_INITIATOR --group "$CY_GROUP" --spec "$CY_SPEC" --config "$CY_CONFIG" ``` ## Using Cypress Cloud with AWS CodeBuild Dashboard analytics depend on Cypress receiving [git commit information](/llm/markdown/app/continuous-integration/overview.md#Git-information). The `buildspec.yml` examples above export this via `git` CLI commands, which require a `.git` directory. If CodeBuild downloads your source as a zip archive instead of performing a `git clone`, those commands will fail with `fatal: not a git repository`. To fix this, configure your CodeBuild project to clone the repository using the [Git clone depth setting](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html) in the AWS Console. If git clone is not an option, CodeBuild always populates `CODEBUILD_RESOLVED_SOURCE_VERSION` with the commit SHA, which you can use to retrieve commit metadata from your source provider's API instead. If you are still facing issues, please [contact us](mailto:hello@cypress.io). In the AWS CodeBuild configuration we have defined in the previous section, we are leveraging three useful features of [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md): 1. [Recording test results with the `--record` flag](/llm/markdown/cloud/get-started/setup.md) to Cypress Cloud. * In-depth and shareable [test reports](/llm/markdown/cloud/features/recorded-runs.md#Latest-Runs). * Visibility into test failures via quick access to [Test Replay](/llm/markdown/cloud/features/test-replay.md), error messages, stack traces, screenshots, videos, and contextual details. * [Integrating testing with the pull-request (PR) process](/llm/markdown/cloud/integrations/github.md) via [commit status check guards](/llm/markdown/cloud/integrations/github.md#Status-checks) and convenient [test report comments](/llm/markdown/cloud/integrations/github.md#Pull-request-comments). * [Detecting flaky tests](/llm/markdown/cloud/features/flaky-test-management.md) and surfacing them via [Slack alerts](/llm/markdown/cloud/features/flaky-test-management.md#Slack) or [GitHub PR status checks](/llm/markdown/cloud/features/flaky-test-management.md#GitHub). 2. [Parallelizing test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) and optimizing their execution via [intelligent load-balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md#Balance-strategy) of test specs across CI machines with the `--parallel` flag. 3. Organizing and consolidating multiple `cypress run` calls by labeled groups into a single report within [Cypress Cloud](https://on.cypress.io/cloud). In the example above we use the `--group UI-Chrome` flag to organize all UI tests for the Chrome browser into a group labeled "UI-Chrome" inside the Cypress Cloud report. --- Source: https://docs.cypress.io/app/continuous-integration/bitbucket-pipelines.md Section: Cypress App # Run Cypress in Bitbucket Pipelines Detailed documentation is available in the [Bitbucket Pipelines Documentation](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/). Bitbucket runs most builds in Docker containers as described in [Docker image options](https://support.atlassian.com/bitbucket-cloud/docs/docker-image-options/). If you use the currently available default Bitbucket / Atlassian Linux images listed in the "Default build environment" of the Bitbucket Cloud documentation [Use Docker images as build environments](https://support.atlassian.com/bitbucket-cloud/docs/use-docker-images-as-build-environments/) you must additionally install [Cypress Linux prerequisites](/llm/markdown/app/get-started/install-cypress.md#Linux-Prerequisites). For a simpler setup, use a Cypress Docker image, as described in the following section. ## Testing with Cypress Docker Images The Cypress team maintains the official [Docker Images](https://github.com/cypress-io/cypress-docker-images) for running Cypress locally and in CI, with some images including Chrome, Firefox and Edge. For example, this allows us to run the tests in Firefox by passing the `--browser firefox` attribute to `cypress run`. Read about [Cypress Docker variants](/llm/markdown/app/continuous-integration/overview.md#Cypress-Docker-variants) to decide which image is best for your project. When starting a local server in CI, use [`wait-on`](https://github.com/jeffbski/wait-on) or [`start-server-and-test`](https://github.com/bahmutov/start-server-and-test) to ensure the server is ready before running Cypress tests. Running `npm start & cypress run` without waiting for the server can cause tests to fail if the server hasn't finished booting. See [Boot your server](/llm/markdown/app/continuous-integration/overview.md#Boot-your-server) for more details. bitbucket-pipelines.yml ``` image: cypress/browsers:22.15.0 pipelines: default: - step: script: # install dependencies - npm ci # start the server in the background - npm run start & # wait for the server to respond (replace with your server's URL) - npx wait-on http://localhost:3000 # run Cypress tests in Firefox - npx cypress run --browser firefox ``` **How this `bitbucket-pipelines.yml` works:** * On _push_ to this repository, this job will provision and start Bitbucket Pipelines using the Cypress Docker image. It will run the pipelines defined in the `pipelines` section of the configuration. * The code is checked out from the Bitbucket repository. * Finally, our scripts will: * Install npm dependencies * Start the project web server (`npm start`) in the background * Wait for the server to be available using `wait-on` * Run the Cypress tests within the Bitbucket repository using Firefox ## Caching Dependencies and Build Artifacts Per the [Caches documentation](https://support.atlassian.com/bitbucket-cloud/docs/cache-dependencies/), Bitbucket offers options for caching dependencies and build artifacts across many different workflows. To cache `node_modules`, the npm cache across builds, the `cache` attribute and configuration has been added below. Artifacts from a job can be defined by providing paths to the `artifacts` attribute. bitbucket-pipelines.yml ``` image: cypress/browsers:22.15.0 pipelines: default: - step: caches: - node script: # install dependencies - npm ci # start the server in the background - npm run start & # wait for the server to respond (replace with your server's URL) - npx wait-on http://localhost:3000 # run Cypress tests in Firefox - npx cypress run --browser firefox artifacts: # store any generates images and videos as artifacts - cypress/screenshots/** - cypress/videos/** ``` Using the [definitions](https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/#Global-configuration-options) block we can define additional caches for npm and Cypress. bitbucket-pipelines.yml ``` definitions: caches: npm: $HOME/.npm cypress: $HOME/.cache/Cypress ``` ## Parallelization [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md) offers the ability to [parallelize and group test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) along with additional insights and [analytics](/llm/markdown/cloud/features/analytics/overview.md) for Cypress tests. Before diving into an example of a parallelization setup, it is important to understand the two different types of jobs that we will declare: * **Install Job**: A job that installs and caches dependencies that will be used by subsequent jobs later in the Bitbucket Pipelines workflow. * **Worker Job**: A job that handles execution of Cypress tests and depends on the _install job_. ### Install Job The separation of installation from test running is necessary when running parallel jobs. It allows for reuse of various build steps aided by caching. First, we break the pipeline up into reusable chunks of configuration using a [YAML anchor](https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/), `&e2e`. This will be used by the worker jobs. The following configuration using the `--parallel` and `--record` flags to [cypress run](/llm/markdown/app/references/command-line.md#cypress-run) requires setting up recording test results to [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md). bitbucket-pipelines.yml ``` image: cypress/base:22.15.0 ## job definition for running E2E tests in parallel e2e: &e2e name: E2E tests caches: - node - cypress script: - npm run start & - npx wait-on http://localhost:3000 - npm run e2e:record -- --parallel --group UI-Chrome --ci-build-id $BITBUCKET_BUILD_NUMBER artifacts: # store any generates images and videos as artifacts - cypress/screenshots/** - cypress/videos/** ``` ### Worker Jobs Next, the worker jobs under `pipelines` that will run Cypress tests with Chrome in parallel. We can use the `e2e` [YAML anchor](https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/) in our definition of the pipeline to execute parallel jobs using the `parallel` attribute. This will allow us to run multiples instances of Cypress at same time. bitbucket-pipelines.yml ``` ## job definition for running E2E tests in parallel ## ... pipelines: default: - step: name: Install dependencies caches: - npm - cypress - node script: - npm ci - parallel: # run N steps in parallel - step: <<: *e2e - step: <<: *e2e - step: <<: *e2e definitions: caches: npm: $HOME/.npm cypress: $HOME/.cache/Cypress ``` The complete `bitbucket-pipelines.yml` is below: bitbucket-pipelines.yml ``` image: cypress/base:22.15.0 ## job definition for running E2E tests in parallel e2e: &e2e name: E2E tests caches: - node - cypress script: - npm run start & - npx wait-on http://localhost:3000 - npm run e2e:record -- --parallel --group UI-Chrome --ci-build-id $BITBUCKET_BUILD_NUMBER artifacts: # store any generates images and videos as artifacts - cypress/screenshots/** - cypress/videos/** pipelines: default: - step: name: Install dependencies caches: - npm - cypress - node script: - npm ci - parallel: # run N steps in parallel - step: <<: *e2e - step: <<: *e2e - step: <<: *e2e definitions: caches: npm: $HOME/.npm cypress: $HOME/.cache/Cypress ``` ## Using Cypress Cloud with Bitbucket Pipelines In the Bitbucket Pipelines configuration we have defined in the previous section, we are leveraging three useful features of [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md): 1. [Recording test results with the `--record` flag](/llm/markdown/cloud/get-started/setup.md) to Cypress Cloud. * In-depth and shareable [test reports](/llm/markdown/cloud/features/recorded-runs.md#Latest-Runs). * Visibility into test failures via quick access to [Test Replay](/llm/markdown/cloud/features/test-replay.md), error messages, stack traces, screenshots, videos, and contextual details. * [Integrating testing with the pull-request (PR) process](/llm/markdown/cloud/integrations/github.md) via [commit status check guards](/llm/markdown/cloud/integrations/github.md#Status-checks) and convenient [test report comments](/llm/markdown/cloud/integrations/github.md#Pull-request-comments). * [Detecting flaky tests](/llm/markdown/cloud/features/flaky-test-management.md) and surfacing them via [Slack alerts](/llm/markdown/cloud/features/flaky-test-management.md#Slack) or [GitHub PR status checks](/llm/markdown/cloud/features/flaky-test-management.md#GitHub). 2. [Parallelizing test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) and optimizing their execution via [intelligent load-balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md#Balance-strategy) of test specs across CI machines with the `--parallel` flag. 3. Organizing and consolidating multiple `cypress run` calls by labeled groups into a single report within [Cypress Cloud](https://on.cypress.io/cloud). In the example above we use the `--group UI-Chrome` flag to organize all UI tests for the Chrome browser into a group labeled "UI-Chrome" inside the Cypress Cloud report. --- Source: https://docs.cypress.io/app/continuous-integration/circleci.md Section: Cypress App # Run Cypress in CircleCI The [Cypress CircleCI Orb](https://circleci.com/developer/orbs/orb/cypress-io/cypress) is the _official_ CircleCI Orb of Cypress. Although you don't need to use the orb to run your tests in CircleCI, the benefit of using the orb is that it allows you to easily install, cache and run Cypress tests in CircleCI with less effort. The orb abstracts common steps necessary for running your tests in CircleCI in order to make your life as a developer better! ## Basic Setup The [Cypress CircleCI Orb](https://github.com/cypress-io/circleci-orb) is a piece of configuration set in your `.circleci/config.yml` file to correctly install, cache and run Cypress with very little effort. For the Orb Quick Start Guide and usage cases, view the CircleCI [Cypress orb documentation](https://circleci.com/developer/orbs/orb/cypress-io/cypress). A typical project can have: .circleci/config.yml ``` version: 2.1 orbs: # "cypress-io/cypress@6" installs the latest published # version "s.x.y" of the orb. We recommend you then use # the strict explicit version "cypress-io/cypress@6.x.y" # to lock the version and prevent unexpected CI changes cypress: cypress-io/cypress@6 workflows: build: jobs: - cypress/run: # "run" job comes from "cypress" orb start-command: 'npm run start' ``` That's it! Your repo's dependencies will be installed and cached and your Cypress tests will run in CircleCI ## Parallelization A more complex project that needs to install dependencies, start a server, and run tests across 4 CI machines [in parallel](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) may have: .circleci/config.yml ``` version: 2.1 orbs: cypress: cypress-io/cypress@6 workflows: build: jobs: - cypress/run: start-command: 'npm run start' cypress-command: 'npx cypress run --parallel --record --group all tests' parallelism: 4 ``` Using the orb brings simplicity and static checks of parameters to CircleCI configuration. You can find additional examples at [our orb examples page](https://github.com/cypress-io/circleci-orb/blob/master/src/examples). The Cypress [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app) uses the Circle CI [Cypress Orb](https://github.com/cypress-io/circleci-orb), Codecov Orb, and Windows Orb to test over 300 test cases in parallel across 25 machines, multiple browsers, multiple device sizes, and multiple operating systems with full code-coverage reporting and [Cypress Cloud recording](https://cloud.cypress.io/projects/7s5okt). Check out the full [RWA Circle CI configuration](https://github.com/cypress-io/cypress-realworld-app/blob/develop/.circleci/config.yml). ## Additional Examples ### Component Testing Example .circleci/config.yml ``` version: 2.1 orbs: cypress: cypress-io/cypress@6 workflows: test: jobs: - cypress/run: cypress-command: 'npx cypress run --component' ``` ### Yarn Example .circleci/config.yml ``` version: 2.1 orbs: cypress: cypress-io/cypress@6 workflows: test: jobs: - cypress/run: package-manager: 'yarn' start-command: 'yarn start' ``` ### Chrome Example The `install-browsers` flag installs Chrome, Chrome for Testing, Edge, Firefox and the geckodriver. Use it together with the `--browser` flag in your `cypress-command` to run your tests in an installed browser. .circleci/config.yml ``` version: 2.1 orbs: cypress: cypress-io/cypress@6 workflows: test: jobs: - cypress/run: install-browsers: true start-command: 'npm run start' cypress-command: 'npx cypress run --browser chrome' ``` --- Source: https://docs.cypress.io/app/continuous-integration/github-actions.md Section: Cypress App # Run Cypress in GitHub Actions GitHub offers developers [Actions](https://github.com/features/actions) that provide a way to **automate, customize, and execute your software development workflows** within your GitHub repository. Detailed documentation is available in the [GitHub Action Documentation](https://docs.github.com/en/actions). ## GitHub Actions + Cypress Screencasts 1. [What is Continuous Integration?](https://youtu.be/USX6AntcPyg) 2. [Actions & Workflows](https://youtu.be/N0TOFWy1Xvg) 3. [Understanding how to configure a workflow](https://youtu.be/vVr7DXDdUks) 4. [Running Tests in GitHub Actions CI/CD Workflow](https://youtu.be/23ZGSrmbV_4) 5. [Debugging Test Failures in CI](https://youtu.be/Oqq-_QZWzhg) 6. [Running Tests in Parallel](https://youtu.be/96Yn_IiQUJI) ## Cypress GitHub Action Workflows can be packaged and shared as [GitHub Actions](https://github.com/features/actions). GitHub maintains many, such as the [checkout](https://github.com/marketplace/actions/checkout) and [Upload/Download Artifact Actions](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) actions used below. The official [Cypress GitHub Action](https://github.com/marketplace/actions/cypress-io) is maintained by Cypress and our community to help ease the setup of Cypress in a GitHub Action. The action provides dependency installation (via npm, yarn, or pnpm), built-in caching of Node dependencies, and additional configuration options for advanced workflows. ### Version Number Selection **GitHub Action Version Number** We recommend binding to the action's latest major version by specifying `v7` when using the action. For Example: ``` jobs: cypress-run: steps: - uses: cypress-io/github-action@v7 ``` Alternatively, as a mitigation strategy for unforeseen breaks, bind to a specific [release version tag](https://github.com/cypress-io/github-action/releases), for example `cypress-io/github-action@v7.1.0`. Read the [Cypress GitHub Action documentation](https://github.com/cypress-io/github-action#action-version) for more information. ## Basic Setup The example below is a basic CI setup and job using the [Cypress GitHub Action](https://github.com/marketplace/actions/cypress-io) to run Cypress tests. This GitHub Action configuration is placed within `.github/workflows/main.yml`. ``` name: Cypress Tests on: push jobs: cypress-run: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v7 # Install npm dependencies, cache them correctly # and run all Cypress tests - name: Cypress run uses: cypress-io/github-action@v7 with: build: npm run build start: npm start ``` **How this action works:** * On _push_ to this repository, this job will provision and start a GitHub-hosted Ubuntu Linux instance to run the outlined `steps` for the declared `cypress-run` job within the `jobs` section of the configuration. * The [GitHub checkout Action](https://github.com/marketplace/actions/checkout) is used to check out our code from our GitHub repository. * Finally, our Cypress GitHub Action will: * Install npm dependencies * Build the project (`npm run build`) * Start the project web server (`npm start`) * Run the Cypress tests within our GitHub repository. To run in a specific browser such as Chrome, see [Testing on GitHub with Installed Browsers](#Testing-on-GitHub-with-Installed-Browsers). ## Testing on GitHub with Installed Browsers [GitHub-hosted runners](https://github.com/actions/runner-images) offer images with pre-installed browsers to use for testing. The `ubuntu` and `windows` runners each include Google Chrome, Mozilla Firefox, and Microsoft Edge pre-installed. The `macos` runners additionally include Apple Safari. Refer to [GitHub Actions Runner Images](https://github.com/actions/runner-images/blob/main/README.md) for current details. Use the action's `browser` parameter to select the desired browser. To change the above example to select Chrome, add `browser: chrome` as follows. ``` - name: Cypress run uses: cypress-io/github-action@v7 with: build: npm run build start: npm start browser: chrome ``` For more examples, see the action's [Browser](https://github.com/cypress-io/github-action#browser) section. If you are specifying a browser in a parallel job, see [Specifying Browsers in Parallel Builds](#Specifying-Browsers-in-Parallel-Builds) for more info on how to avoid errors during runs due to GitHub runner images being updated with the latest browsers. ## Testing with Cypress Docker Images GitHub Actions provides the option to specify a container image for the job. Cypress offers various [Docker Images](https://github.com/cypress-io/cypress-docker-images) for running Cypress locally and in CI. Note that GitHub Actions [requires using a Linux runner](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idcontainer) when specifying a container image. It does not support running Docker images on Windows or macOS runners. Below, we extend the previous example by adding the `container` attribute using a [cypress/browsers](https://github.com/cypress-io/cypress-docker-images/tree/master/browsers) Docker image which also includes Google Chrome. We are using the Node.js short-form tag to select a `cypress/browsers` image built with the corresponding Node.js version. To specify instead an exact set of browser versions, visit the [Docker Hub cypress/browsers](https://hub.docker.com/r/cypress/browsers) page and view the available long-form tags, for example `cypress/browsers:node-22.15.0-chrome-136.0.7103.92-1-ff-138.0.1-edge-136.0.3240.50-1`. Using a Cypress Docker image allows our tests to execute without any influence from browser version changes in the GitHub runner image. ``` name: Cypress Tests using Cypress Docker Image on: push jobs: cypress-run: runs-on: ubuntu-24.04 container: image: cypress/browsers:22.15.0 options: --user 1001 steps: - name: Checkout uses: actions/checkout@v7 - name: Cypress run uses: cypress-io/github-action@v7 with: build: npm run build start: npm start browser: chrome ``` If you are testing with Firefox, you must specify the non-root user `1001` as above. Refer to [Firefox not found](https://github.com/cypress-io/cypress-docker-images#firefox-not-found) for more information. ## Caching Dependencies and Build Artifacts When working with actions that have multiple jobs, it is recommended to have an initial "install" job that will download any dependencies and build your app, and then cache these assets for use later by subsequent jobs. The Cypress GitHub Action will automatically cache and restore your Node dependencies for you. For build assets, you will need to cache and restore them manually. The `install` job below uses the [upload-artifact](https://github.com/marketplace/actions/upload-a-build-artifact) action and saves the state of the `build` directory for the `cypress-run` worker job. The [download-artifact](https://github.com/marketplace/actions/download-a-build-artifact) action retrieves the `build` directory saved in the `install` job, as seen below in the `cypress-run` worker job. ``` name: Cypress Tests with Dependency and Artifact Caching on: push jobs: install: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v7 - name: Cypress install uses: cypress-io/github-action@v7 with: # Disable running of tests within install job runTests: false build: npm run build - name: Save build folder uses: actions/upload-artifact@v6 with: name: build if-no-files-found: error path: build cypress-run: runs-on: ubuntu-24.04 needs: install steps: - name: Checkout uses: actions/checkout@v7 - name: Download the build folder uses: actions/download-artifact@v7 with: name: build path: build - name: Cypress run uses: cypress-io/github-action@v7 with: start: npm start browser: chrome ``` View GitHub's guide on [Storing workflow data as artifacts](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) for more info. ## Parallelization [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md) offers the ability to [parallelize and group test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) along with additional insights and [analytics](/llm/markdown/cloud/features/analytics/overview.md) for Cypress tests. Using parallelization with the Cypress GitHub Action requires setting up recording to [Cypress Cloud](https://on.cypress.io/cloud). GitHub Actions offers a [matrix strategy](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) for declaring different job configurations for a single job definition. Jobs declared within a matrix strategy can run in parallel, which enables us to run multiples instances of Cypress at the same time, as we will see later in this section. Before diving into an example of a parallelization setup, it is important to understand the two different types of GitHub Action jobs that we will declare: * **Install Job**: A job that installs and caches dependencies that will be used by subsequent jobs later in the GitHub Action workflow. * **Worker Job**: A job that handles the execution of Cypress tests and depends on the _install job_. ### Install Job The separation of installation from test running is necessary when running parallel jobs. It allows for the reuse of various build steps aided by caching. First, we'll define the `install` step that will be used by the worker jobs defined in the matrix strategy. For the `steps`, notice that we pass `runTests: false` to the Cypress GitHub Action to instruct it only to install and cache Cypress and npm dependencies _without running the tests_. The [upload-artifact](https://github.com/marketplace/actions/upload-a-build-artifact) action will save the state of the `build` directory for the worker jobs. ``` name: Cypress Tests on: push jobs: install: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v7 - name: Cypress install uses: cypress-io/github-action@v7 with: # Disable running of tests within install job runTests: false build: npm run build - name: Save build folder uses: actions/upload-artifact@v6 with: name: build if-no-files-found: error path: dist ``` ### Worker Jobs Next, we define the worker job named "cypress-run" that will run Cypress tests as part of a parallelized matrix strategy. The `download-artifact` action will retrieve the **dist** directory saved in the install job. ``` name: Cypress Tests on: push jobs: install: # ... omitted install job from above cypress-run: runs-on: ubuntu-24.04 needs: install strategy: # don't fail the entire matrix on failure fail-fast: false matrix: # run copies of the current job in parallel containers: [1, 2, 3, 4, 5] steps: - name: Checkout uses: actions/checkout@v7 - name: Download the build folder uses: actions/download-artifact@v7 with: name: build - name: Cypress run uses: cypress-io/github-action@v7 with: record: true parallel: true group: 'UI-Chrome' start: npm start ``` **Ensure Correct Container** If a Docker container was used in the install job, the same Docker container must also be used in the worker jobs. #### Setting up Parallelization To set up multiple containers to run in parallel, the matrix option of the strategy configuration can be set to containers: \[1, 2, 3, 4, 5\], where the number of items defined in the containers array will be how many instances of the job will start up. For instance, `containers: [1, 2, 3, 4, 5]` will provision five worker instances to run in parallel. For our purposes, the array's values are arbitrary and aren't used in the steps. ### Specifying Browsers in Parallel Builds When GitHub deploys new runner image versions containing updated browser versions, and the deployment is still in progress, the workflow "Set up job" phase randomly uses either an old or a new runner image version. Your test run might fail if Cypress Cloud detects differences in the browser versions between parallel jobs. To work around this issue, we recommend using a [cypress/browsers](#Testing-with-Cypress-Docker-Images) Docker image, which uses one consistent browser version. This shields the workflow from browser version changes due to possible incomplete GitHub runner image deployments. As mentioned in [Testing with Cypress Docker Images](#Testing-with-Cypress-Docker-Images), this option is only available with GitHub Actions Linux runners. ## Using Cypress Cloud with GitHub Actions In the GitHub Actions configuration, we have defined in the previous section, we are leveraging three useful features of [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md): 1. [Recording test results with the `record: true` option](/llm/markdown/cloud/get-started/setup.md) to [Cypress Cloud](https://on.cypress.io/cloud): * In-depth and shareable [test reports](/llm/markdown/cloud/features/recorded-runs.md#Latest-Runs). * Visibility into test failures via quick access to [Test Replay](/llm/markdown/cloud/features/test-replay.md), error messages, stack traces, screenshots, videos, and contextual details. * [Integrating testing with the pull-request (PR) process](/llm/markdown/cloud/integrations/github.md) via [commit status check guards](/llm/markdown/cloud/integrations/github.md#Status-checks) and convenient [test report comments](/llm/markdown/cloud/integrations/github.md#Pull-request-comments). * [Detecting flaky tests](/llm/markdown/cloud/features/flaky-test-management.md) and surfacing them via [Slack alerts](/llm/markdown/cloud/features/flaky-test-management.md#Slack) or [GitHub PR status checks](/llm/markdown/cloud/features/flaky-test-management.md#GitHub). 2. [Parallelizing test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) and optimizing their execution via [intelligent load-balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md#Balance-strategy) of test specs across CI machines with the `parallel: true` option. 3. Organizing and consolidating multiple `cypress run` calls by labeled groups into a single report within [Cypress Cloud](https://on.cypress.io/cloud). In the example above, we use the `group: "UI-Chrome"` option to organize all UI tests for the Chrome browser into a group labeled "UI - Chrome" in the [Cypress Cloud](https://on.cypress.io/cloud) report. ## Cypress Real World Example with GitHub Actions A complete CI workflow against multiple browsers, viewports, and operating systems is available in the **Cypress Real World App**. Clone the [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app) and refer to the [.github/workflows/main.yml](https://github.com/cypress-io/cypress-realworld-app/blob/develop/.github/workflows/main.yml) file. To see additional how-to examples, you can also refer to our [Cypress GitHub Action repo](https://github.com/cypress-io/github-action). ## Common Problems and Solutions ### Re-run jobs passing with empty tests We recommend passing the `GITHUB_TOKEN` secret (created by the GH Action automatically) as a system environment variable in CI/CD. This will allow the accurate identification of each build to avoid confusion when re-running a build. ``` name: Cypress tests on: push jobs: cypress-run: name: Cypress run runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v7 - name: Cypress run uses: cypress-io/github-action@v7 with: record: true env: # pass GitHub token to detect new build vs re-run build GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} ``` ### Pull requests commit message is `merge SHA into SHA` You can overwrite the commit message sent to Cypress Cloud by setting a system environment variable in CI/CD. See [Issue #124](https://github.com/cypress-io/github-action/issues/124) for more details. ``` name: Cypress tests on: push jobs: cypress-run: name: Cypress run runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v7 - name: Cypress run uses: cypress-io/github-action@v7 with: record: true env: # overwrite commit message sent to Cypress Cloud COMMIT_INFO_MESSAGE: ${{github.event.pull_request.title}} # re-enable PR comment bot COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha}} ``` We also recommend adding `COMMIT_INFO_SHA` to re-enable [Cypress bot PR comments](/llm/markdown/cloud/integrations/github.md#Pull-request-comments). See [this comment](https://github.com/cypress-io/github-action/issues/124#issuecomment-716584972) for more details. ## See also * [Test anything that runs in the browser with Cypress and GitHub Actions](https://www.youtube.com/watch?v=gokM_zEmWLA) --- Source: https://docs.cypress.io/app/continuous-integration/gitlab-ci.md Section: Cypress App # Run Cypress in GitLab CI With its hosted [CI/CD Service](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/), [GitLab](https://gitlab.com) offers developers "a tool built into GitLab for software development through the continuous methodologies". Detailed documentation is available in the [GitLab CI/CD Documentation](https://docs.gitlab.com/ee/ci/). ## Basic Setup The example below is basic CI setup and job using [GitLab CI/CD](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) to run Cypress tests. This GitLab CI configuration is placed within `.gitlab-ci.yml`. ``` stages: - test test: image: node:latest stage: test script: # install dependencies - npm ci # start the server in the background - npm start & # run Cypress tests - npm run e2e ``` **How this configuration works:** * On _push_ to this repository, this job will provision and start GitLab-hosted Linux instance for running the outlined `stages` declared in `script` within the `test` job section of the configuration. * The code is checked out from the GitLab repository. * Finally, our scripts will: * Install npm dependencies * Start the project web server (`npm start`) * Run the Cypress tests within the GitLab repository. To run in an installed browser like Chrome or Firefox, see [Testing with Cypress Docker Images](#Testing-with-Cypress-Docker-Images) below. ## Testing with Cypress Docker Images The Cypress team maintains the official [Docker Images](https://github.com/cypress-io/cypress-docker-images) for running Cypress tests locally and in CI, which are built with Google Chrome, Mozilla Firefox and Microsoft Edge. For example, this allows us to run the tests in Firefox by passing the `--browser firefox` attribute to `cypress run`. ``` stages: - test test: image: cypress/browsers:22.15.0 stage: test script: # install dependencies - npm ci # start the server in the background - npm start & # run Cypress tests - npx cypress run --browser firefox ``` ## Caching Dependencies and Build Artifacts Caching of dependencies and build artifacts can be accomplished with the `cache` configuration. The [caching documentation](https://docs.gitlab.com/ee/ci/caching/) contains all options for caching dependencies and build artifacts across many different workflows. Artifacts from a job can be defined by providing paths and an optional expiry time. ``` stages: - test cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ - .npm/ test: image: cypress/browsers:22.15.0 stage: test script: # install dependencies - npm ci # start the server in the background - npm start & # run Cypress tests - npx cypress run --browser firefox artifacts: when: always paths: - cypress/videos/**/*.mp4 - cypress/screenshots/**/*.png expire_in: 1 day ``` ## Parallelization [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md) offers the ability to [parallelize and group test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) along with additional insights and [analytics](/llm/markdown/cloud/features/analytics/overview.md) for Cypress tests. The addition of the [`parallel` attribute](https://docs.gitlab.com/ee/ci/yaml/#parallel) to the configuration of a job will allow us to run multiples instances of Cypress at same time as we will see later in this section. Before diving into an example of a parallelization setup, it is important to understand the two different types of jobs that we will declare: * **Install Job**: A job that installs and caches dependencies that will be used by subsequent jobs later in the GitLab CI workflow. * **Worker Job**: A job that handles execution of Cypress tests and depends on the _install job_. ### Install Job The separation of installation from test running is necessary when running parallel jobs. It allows for reuse of various build steps aided by caching. First, we will define the `build` stage along with `cache` and variables related to the cache. Then we define the `install` step that will be used by the worker jobs and assign it to the `build` stage. ``` stages: - build ## Set system environment variables for folders in "cache" job settings ## for npm modules and Cypress binary variables: npm_config_cache: '$CI_PROJECT_DIR/.npm' CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress' cache: key: ${CI_COMMIT_REF_SLUG} paths: - cache/Cypress - node_modules - build ## Install npm dependencies and Cypress install: image: cypress/browsers:22.15.0 stage: build script: - npm ci ``` ### Worker Jobs Next, we add a `test` stage and define the worker job named `ui-chrome-tests` that will run Cypress tests with Chrome in parallel during the `test` stage. The addition of the [`parallel` attribute](https://docs.gitlab.com/ee/ci/yaml/#parallel) to the configuration of a job will allow us to run multiples instances of Cypress at same time. The following configuration using the `--parallel` and `--record` flags to [cypress run](/llm/markdown/app/references/command-line.md#cypress-run) requires setting up recording test results to [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md). ``` stages: - build - test ## Set system environment variables for folders in "cache" job settings ## for npm modules and Cypress binary variables: npm_config_cache: '$CI_PROJECT_DIR/.npm' CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress' cache: key: ${CI_COMMIT_REF_SLUG} paths: - .cache/* - cache/Cypress - node_modules - build ## Install npm dependencies and Cypress install: image: cypress/browsers:22.15.0 stage: build script: - npm ci ui-chrome-tests: image: cypress/browsers:22.15.0 stage: test parallel: 5 script: # install dependencies - npm ci # start the server in the background - npm start & # run Cypress tests in parallel - npx cypress run --record --parallel --browser chrome --group UI-Chrome ``` ## Using Cypress Cloud with GitLab CI/CD In the GitLab CI configuration we have defined in the previous section, we are leveraging three useful features of [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md): 1. [Recording test results with the `--record` flag](/llm/markdown/cloud/get-started/setup.md) to Cypress Cloud. * In-depth and shareable [test reports](/llm/markdown/cloud/features/recorded-runs.md#Latest-Runs). * Visibility into test failures via quick access to [Test Replay](/llm/markdown/cloud/features/test-replay.md), error messages, stack traces, screenshots, videos, and contextual details. * [Integrating testing with the pull-request (PR) process](/llm/markdown/cloud/integrations/github.md) via [commit status check guards](/llm/markdown/cloud/integrations/github.md#Status-checks) and convenient [test report comments](/llm/markdown/cloud/integrations/github.md#Pull-request-comments). * [Detecting flaky tests](/llm/markdown/cloud/features/flaky-test-management.md) and surfacing them via [Slack alerts](/llm/markdown/cloud/features/flaky-test-management.md#Slack) or [GitHub PR status checks](/llm/markdown/cloud/features/flaky-test-management.md#GitHub). 2. [Parallelizing test runs](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) and optimizing their execution via [intelligent load-balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md#Balance-strategy) of test specs across CI machines with the `--parallel` flag. 3. Organizing and consolidating multiple `cypress run` calls by labeled groups into a single report within [Cypress Cloud](https://on.cypress.io/cloud). In the example above we use the `--group UI-Chrome` flag to organize all UI tests for the Chrome browser into a group labeled "UI-Chrome" inside the Cypress Cloud report. --- Source: https://docs.cypress.io/app/continuous-integration/overview.md Section: Cypress App # Continuous Integration with Cypress Cypress runs in any continuous integration (CI) provider, including GitHub Actions, CircleCI, GitLab CI, Jenkins, and AWS CodeBuild. Running your Cypress tests on every push and pull request catches regressions before they reach your users, and recorded results show you exactly what happened when a test fails. Setting it up takes two commands: install Cypress, then run it. This guide covers those commands and everything around them: booting your app's server, choosing a Docker image, caching, parallelization, and setup guides for each major CI provider. ## What is Continuous Integration? Continuous integration is the practice of merging code changes into a shared repository frequently, where each change is verified by an automated build and test run. When Cypress is part of that pipeline, every commit runs your end-to-end and component tests against your application, so a change that breaks a user flow fails the build instead of shipping. ## Setting up CI ### Install and run Cypress Running Cypress in CI is almost the same as running it locally in your terminal. You generally only need to do two things: 1. **Install Cypress** * npm * Yarn * pnpm * Bun ``` npm install cypress --save-dev ``` ``` yarn add cypress --dev ``` ``` pnpm add --save-dev cypress ``` ``` bun add --dev cypress ``` 1. **Run Cypress** * npm * Yarn * pnpm * Bun ``` npx cypress run ``` ``` yarn cypress run ``` ``` pnpm cypress run ``` ``` bunx cypress run ``` Where these commands go depends on your CI provider: each defines its own configuration file format for the steps in a build. Refer to your CI provider's documentation for where to add the commands to install and run Cypress, or start from one of our [CI examples](#CI-Examples). ### Boot your server Typically you will need to boot a local server before running Cypress. A web server is a **long running process** that never exits, so you need to run it in the **background** or your CI provider will never move on to the next command. You also need to wait for the server to be ready before Cypress starts. A command like this has a race condition: ``` npm start & npx cypress run # don't do this ``` There is no guarantee that your server has booted by the time `cypress run` executes, so your tests may try to visit your local server before it is ready. Instead of adding an arbitrary wait (like `sleep 20`), use a tool that waits for the server to respond. If you run your tests with the official [Cypress GitHub Action](https://github.com/cypress-io/github-action), its `start` and `wait-on` options boot and wait for your server without any extra packages. See our [GitHub Actions guide](/llm/markdown/app/continuous-integration/github-actions.md). #### `start-server-and-test` module The [start-server-and-test](https://github.com/bahmutov/start-server-and-test) module starts your server, waits until a URL responds, runs your test command, and shuts the server down when the tests finish. * npm * Yarn * pnpm * Bun ``` npm install start-server-and-test --save-dev ``` ``` yarn add start-server-and-test --dev ``` ``` pnpm add --save-dev start-server-and-test ``` ``` bun add --dev start-server-and-test ``` In your `package.json` scripts, pass the command that boots your server, the URL your server is hosted on, and your Cypress test command. package.json ``` { "scripts": { "start": "my-server -p 3030", "cy:run": "cypress run", "test": "start-server-and-test start http://localhost:3030 cy:run" } } ``` In the example above, the `cy:run` command will only be executed when the URL `http://localhost:3030` responds with an HTTP status code of 200. The server also shuts down when the tests complete. **Gotchas** When [working with `webpack-dev-server`](https://github.com/bahmutov/start-server-and-test#note-for-webpack-dev-server-users) or another server that does not respond to `HEAD` requests, use an explicit `GET` method to ping the server like this: package.json ``` { "scripts": { "test": "start-server-and-test start http-get://localhost:3030 cy:run" } } ``` When working with local `https` in webpack, set a system environment variable to allow the local certificate: package.json ``` { "scripts": { "start": "my-server -p 3030 --https", "cy:run": "cypress run", "cy:ci": "START_SERVER_AND_TEST_INSECURE=1 start-server-and-test start https-get://localhost:3030 cy:run" } } ``` #### `wait-on` module If you'd rather manage the server process yourself, the [wait-on](https://github.com/jeffbski/wait-on) module blocks until a URL responds. Background the server, wait for it, then run Cypress: ``` npm start & npx wait-on http://localhost:8080 ``` ``` npx cypress run ``` Most CI providers automatically kill background processes, so you don't have to worry about cleaning up your server process once Cypress finishes. However, if you're running this script locally you'll have to do a bit more work to collect the backgrounded PID and then kill it after `cypress run`. #### `concurrently` module A general-purpose process runner like [concurrently](https://github.com/open-cli-tools/concurrently) can compose the same flow: start the server and a `wait-on`\-gated test command together, kill the server when the tests finish (`-k`), and pass or fail based on the test command alone (`-s first`). ``` npx concurrently -k -s first "npm start" "npx wait-on http://localhost:8080 && npx cypress run" ``` ### Record tests Cypress can record your test runs and make the results available in [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md), giving you insight into what happened when your tests ran in CI. Recording tests allows you to: * See the number of failed, pending, and passing tests. * Debug failures with [Test Replay](/llm/markdown/cloud/features/test-replay.md), which captures the state of your application so you can inspect the DOM, network requests, and console logs at every step of the failed test. * Get the entire stack trace of failed tests, or hand that same failure context to your AI coding agent through [Cypress Cloud MCP](/llm/markdown/cloud/integrations/cloud-mcp.md) so it can debug the failing run for you. * View screenshots taken when tests fail and when using [`cy.screenshot()`](/llm/markdown/api/commands/screenshot.md). * Detect and manage [flaky tests](/llm/markdown/cloud/features/flaky-test-management.md) that pass and fail across recorded runs. * Analyze trends in your test suite with [Analytics](/llm/markdown/cloud/features/analytics/overview.md), like run duration, most common errors, and slowest tests. * Generate [UI Coverage](/llm/markdown/ui-coverage/get-started/introduction.md) and [Cypress Accessibility](/llm/markdown/accessibility/get-started/introduction.md) reports from your recorded runs with no additional test code or configuration (premium solutions). * See which machines ran each test when [parallelized](/llm/markdown/cloud/features/smart-orchestration/parallelization.md). To record tests: 1. [Set up your project to record](/llm/markdown/cloud/get-started/setup.md#Setup) and copy your record key. 2. [Pass the `--record` flag to `cypress run`](/llm/markdown/app/references/command-line.md#cypress-run) within CI, along with the record key, either inline with the `--key` flag or via the [`CYPRESS_RECORD_KEY` environment variable](#Environment-variables). * npm * Yarn * pnpm * Bun ``` npx cypress run --record --key=abc123 ``` ``` yarn cypress run --record --key=abc123 ``` ``` pnpm cypress run --record --key=abc123 ``` ``` bunx cypress run --record --key=abc123 ``` [Read the full guide on Cypress Cloud.](/llm/markdown/cloud/get-started/introduction.md) ### Run tests in parallel Cypress can run tests in parallel across multiple machines, cutting your total run time roughly in proportion to the number of machines. You'll want to refer to your CI provider's documentation on how to set up multiple machines to run in your CI environment. Once multiple machines are available within your CI environment, you can pass the [\--parallel](/llm/markdown/app/references/command-line.md#cypress-run-parallel) flag to have your tests run in parallel. Parallelization requires recording to [Cypress Cloud](/llm/markdown/cloud/get-started/introduction.md), which load-balances specs across your machines. * npm * Yarn * pnpm * Bun ``` npx cypress run --record --key=abc123 --parallel ``` ``` yarn cypress run --record --key=abc123 --parallel ``` ``` pnpm cypress run --record --key=abc123 --parallel ``` ``` bunx cypress run --record --key=abc123 --parallel ``` [Read the full guide on parallelization.](/llm/markdown/cloud/features/smart-orchestration/parallelization.md) ## Cypress Docker Images CI providers, such as [GitHub Actions](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) and [CircleCI](https://circleci.com/docs/executor-intro/#docker), allow workflows to run using [Docker container images](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-container/). Cypress supports the use of [Docker](https://docs.docker.com/get-started/docker-overview/) through the provisioning of official [Cypress Docker images](https://github.com/cypress-io/cypress-docker-images). Images are Linux-based and support the following platforms: * Linux/amd64 * Linux/arm64 Cypress Docker images provide a consistent environment tailored for use with Cypress. By choosing an appropriate Cypress Docker image, you determine the exact environment that your Cypress tests run in. This shields your workflows from version updates made by your CI provider, for instance if they update Node.js or browser versions. Cypress Docker images are available from: * [Docker Hub](https://hub.docker.com/u/cypress) * [Amazon ECR (Elastic Container Registry) Public Gallery](https://gallery.ecr.aws/cypress-io) ### Cypress Docker variants * [cypress/base](https://github.com/cypress-io/cypress-docker-images/tree/master/base) is the entry-level Cypress Docker image. To test in an installed browser such as Chrome or Firefox, use the `cypress/browsers` image described below. It contains a complete Linux (Debian) operating system, together with the [prerequisite operating system packages](/llm/markdown/app/get-started/install-cypress.md#UbuntuDebian) for Cypress, Node.js, npm and Yarn v1 Classic. An image `` gives you the choice of Node.js version. * [cypress/browsers](https://github.com/cypress-io/cypress-docker-images/tree/master/browsers) builds on the [cypress/base](https://github.com/cypress-io/cypress-docker-images/tree/master/base) image. For `Linux/amd64` images it adds **Chrome**, **Firefox** and **Edge** browsers. For `Linux/arm64` images it adds **Firefox** browsers from version `136` and above, and **Chrome** browsers from version `151` and above. Edge browsers are currently not available for `Linux/arm64`. A corresponding image `` allows selection of the combined Node.js and browser versions. The version tag for the unavailable Edge browser on the `Linux/arm64` platform is an empty place-holder only, required for multi-platform support compatibility. * [cypress/included](https://github.com/cypress-io/cypress-docker-images/tree/master/included) builds on the [cypress/browsers](https://github.com/cypress-io/cypress-docker-images/tree/master/browsers) image. It adds a fixed version of **Cypress**, globally installed by npm. A short-form image `` selects the version of Cypress. A corresponding long-form `` selects the version of Cypress and documents the combined Node.js and browser versions. * [cypress/factory](https://github.com/cypress-io/cypress-docker-images/tree/master/factory) provides the base operating system image and allows individual selection of other components by version. It is used to generate customized Docker images. ### CI Docker examples You can find examples that use Cypress Docker images below: * [cypress-docker-images examples](https://github.com/cypress-io/cypress-docker-images/blob/master/README.md#examples) * [cypress-example-kitchensink](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/README.md) * [Real World App - CircleCI](https://github.com/cypress-io/cypress-realworld-app/blob/develop/.circleci/config.yml) * [Real World App - GitHub Actions](https://github.com/cypress-io/cypress-realworld-app/blob/develop/.github/workflows/main.yml) * [cypress-docker-images - GitHub Actions](https://github.com/cypress-io/cypress-docker-images/blob/master/.github/workflows/example-cypress-github-action.yml) ## CI Examples We maintain in-depth setup guides for the most popular CI providers, and working example configurations for many others. ### AWS Amplify Console * [Basic Example (amplify.yml)](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/amplify.yml) ### AWS CodeBuild Read our extensive guide on how to set up Cypress in [AWS CodeBuild](/llm/markdown/app/continuous-integration/aws-codebuild.md). ### Azure Pipelines * [Basic Example (azure-ci.yml)](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/basic/azure-ci.yml) * [Parallelized Example (azure-ci.yml)](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/azure-ci.yml) ### Bitbucket Pipelines Read our extensive guide on how to set up Cypress in [Bitbucket Pipelines](/llm/markdown/app/continuous-integration/bitbucket-pipelines.md). ### Buildkite * [Parallel Example](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/.buildkite/pipeline.yml) ### CircleCI Read our extensive guide on how to set up Cypress in [CircleCI](/llm/markdown/app/continuous-integration/circleci.md). ### GitHub Actions Read our extensive guide on how to set up Cypress in [GitHub Actions](/llm/markdown/app/continuous-integration/github-actions.md). ### GitLab Read our extensive guide on how to set up Cypress in [GitLab](/llm/markdown/app/continuous-integration/gitlab-ci.md). ### Jenkins * [Basic Jenkinsfile](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/basic/Jenkinsfile) * [Parallel Jenkinsfile](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/Jenkinsfile) ### Netlify Use our official [netlify-plugin-cypress](https://github.com/cypress-io/netlify-plugin-cypress) to execute end-to-end tests before and after deployment to the Netlify platform. ### Semaphore * [Basic .semaphore.yml](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/basic/.semaphore.yml) * [Parallel .semaphore.yml](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/.semaphore/semaphore.yml) ### TravisCI * [Basic .travis.yml](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/basic/.travis.yml) * [Parallel .travis.yml](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/.travis.yml) ## Advanced setup ### Machine requirements Hardware requirements to run Cypress depend on how much memory the browser, the application under test, and the server (if running it locally) need to run the tests without crashing. Visit our [System Requirements](/llm/markdown/app/get-started/install-cypress.md#System-requirements) guide for minimum hardware recommendations. **Some signs that your machine may not have enough CPU or memory to run Cypress:** * The recorded video artifacts have random pauses or dropped frames. * [Debug logs of the CPU and memory](/llm/markdown/app/references/troubleshooting.md#Log-memory-and-CPU-usage) frequently show CPU percent above 100%. * The browser crashes. You can see the total available machine memory and the current free memory by running the [`cypress info`](/llm/markdown/app/references/command-line.md#cypress-info) command. ``` npx cypress info ... Cypress Version: 15.18.1 (stable) System Platform: linux (Debian - 12) System Memory: 73.6 GB free 48.6 GB ``` You can see the CPU parameters on the CI machines by executing the command below. ``` node -p 'os.cpus()' [ { model: 'Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz', speed: 3399, times: { user: 760580, nice: 1010, sys: 158130, idle: 1638340, irq: 0 } } ... ] ``` **Example projects and the machine configurations used to run them on CI:** * The [Real World App](https://github.com/cypress-io/cypress-realworld-app) project runs tests on a CircleCI machine using the [Docker executor](https://circleci.com/docs/executor-intro/#docker) with [`resource_class: large`](https://circleci.com/docs/configuration-reference/#docker-execution-environment) providing 4 vCPUs and 8 GB of RAM. `cypress info` reports `System Memory: 73.6 GB free 48.6 GB`. * The [Real World App](https://github.com/cypress-io/cypress-realworld-app) project also executes its tests on [GitHub Actions](https://docs.github.com/en/actions) using the [Cypress GitHub Action](https://github.com/cypress-io/github-action) with the [standard Ubuntu GitHub-hosted runner for Public repositories](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners) providing 4 vCPUs and 16 GB of RAM. `cypress info` reports `System Memory: 16.8 GB free 15.5 GB` with CPUs reported as `AMD EPYC 7763 64-Core Processor`. **Tip:** if there are problems with longer specs, try splitting them into shorter ones. ### Dependencies Cypress runs on many CI providers' virtual machine environments out-of-the-box without needing additional dependencies installed. #### Linux If you see a message about a missing dependency when you run Cypress in a Linux CI environment, then refer to the [Linux Prerequisites](/llm/markdown/app/get-started/install-cypress.md#Linux-Prerequisites) lists for guidance. ### Caching Cypress downloads its binary to the global system cache - on Linux that is `~/.cache/Cypress`. By ensuring this cache persists across builds you can save minutes off install time by preventing a large binary download. We recommend that you: * Cache the `~/.cache` folder after running `npm install`, `yarn`, [`npm ci`](https://docs.npmjs.com/cli/ci) or equivalents as demonstrated in the configs below. * **Do not** cache `node_modules` across builds. Instead, cache the package manager's own cache directory (`~/.npm` for npm or `~/.cache/yarn` for yarn). These tools maintain package-level caches that track installed versions, validate integrity, and only re-download packages that have changed. Caching `node_modules` directly bypasses these built-in mechanisms and can cause issues such as Cypress not downloading the Cypress binary on `npm install`. * If you are using `npm install` in your build process, consider [switching to `npm ci`](https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable) and caching the `~/.npm` directory for a faster and more reliable build. * If you are using `yarn`, caching `~/.cache` will include both the `yarn` and Cypress caches. Consider using `yarn install --frozen-lockfile` as an [`npm ci`](https://docs.npmjs.com/cli/ci) equivalent. * If you need to override the binary location for some reason, use the [CYPRESS\_CACHE\_FOLDER](/llm/markdown/app/references/advanced-installation.md#Binary-cache) environment variable. * Make sure you are not restoring the previous cache using lax keys; then the Cypress binaries can "snowball", accumulating every version you have ever installed. **Tip:** you can find lots of CI examples with configured caching in our [cypress-example-kitchensink](https://github.com/cypress-io/cypress-example-kitchensink#ci-status) repository. ### Environment variables You can set various environment variables to modify how Cypress runs. #### Configuration Values You can set any [configuration value](/llm/markdown/app/references/configuration.md) as an environment variable by prefixing it with `CYPRESS_`. This overrides values in the Cypress configuration. **_Typical use cases would be modifying things like:_** * `CYPRESS_BASE_URL` - point tests at a preview or staging deployment * `CYPRESS_REPORTER` - switch to a CI-friendly reporter like `junit` * `CYPRESS_DEFAULT_COMMAND_TIMEOUT` - allow more time on slower CI machines * `CYPRESS_VIEWPORT_WIDTH` and `CYPRESS_VIEWPORT_HEIGHT` - vary the screen size per CI job The install-time variable [`CYPRESS_INSTALL_BINARY`](/llm/markdown/app/references/advanced-installation.md#Download-URLs) is also commonly set in CI, to skip or redirect the binary download. Refer to the [Environment Variables recipe](/llm/markdown/app/references/configuration.md#Environment-Variables) for more examples. **_Record Key_** If you are [recording your runs](#Record-tests) on a public project, you'll want to protect your Record Key. [Learn why.](/llm/markdown/cloud/account-management/projects.md#Project-ID) Instead of hard coding it into your run command like this: * npm * Yarn * pnpm * Bun ``` npx cypress run --record --key abc-key-123 ``` ``` yarn cypress run --record --key abc-key-123 ``` ``` pnpm cypress run --record --key abc-key-123 ``` ``` bunx cypress run --record --key abc-key-123 ``` You can set the record key as the environment variable `CYPRESS_RECORD_KEY` and we'll automatically use that value. You can now omit the `--key` flag when recording. * npm * Yarn * pnpm * Bun ``` npx cypress run --record ``` ``` yarn cypress run --record ``` ``` pnpm cypress run --record ``` ``` bunx cypress run --record ``` Set `CYPRESS_RECORD_KEY` in your CI provider's settings as a secret or masked environment variable so it doesn't appear in your configuration files or build logs. `CYPRESS_RECORD_KEY` must be set as an actual operating system environment variable (for example via `export CYPRESS_RECORD_KEY=...` or your CI provider's secrets). Cypress reads it directly from your shell or CI environment — it is **not** read from `cypress.env.json` or the `env` block of your Cypress configuration, since those only populate test environment variables. If you don't want to set an environment variable, pass the key inline with the `--key` flag instead. #### Git information Cypress assumes there is a `.git` folder and uses Git commands to get each property, like `git show -s --pretty=%B` to get the commit message. Under some environment setups (e.g. `docker`/`docker-compose`) if the `.git` directory is not available or mounted, you can pass all git related information under custom system environment variables. * Branch: `COMMIT_INFO_BRANCH` * Message: `COMMIT_INFO_MESSAGE` * Author email: `COMMIT_INFO_EMAIL` * Author: `COMMIT_INFO_AUTHOR` * SHA: `COMMIT_INFO_SHA` * Remote: `COMMIT_INFO_REMOTE` If the commit information is missing in the Cypress Cloud run then [GitHub Integration](/llm/markdown/cloud/integrations/github.md) or other tasks might not work correctly. To see the relevant Cypress debug logs, set the system environment variable `DEBUG` on your CI machine and inspect the terminal output to see why the commit information is unavailable. ``` DEBUG=cypress:server:record ``` **_Source control credentials_** The remote origin Cypress records comes from `git config --get remote.origin.url`, and some CI providers authenticate the checkout by embedding a token in that URL. Any credential sitting there is readable by every later step in the job and becomes part of the recorded run. Use your CI provider's standard checkout authentication rather than supplying your own long-lived token. Providers issue credentials that expire when the job ends, such as the GitHub Actions `GITHUB_TOKEN`, the GitLab CI/CD job token, a CircleCI checkout key, or the Azure Pipelines `System.AccessToken`. Substituting a personal access token puts a credential that lasts months in the same exposed position. If your checkout leaves a credential in the remote URL, set `COMMIT_INFO_REMOTE` to a clean URL so the recorded remote does not include it. On GitLab CI, for example, `CI_PROJECT_URL` has no token in it: ``` export COMMIT_INFO_REMOTE="$CI_PROJECT_URL.git" ``` #### CI Build Information In some newer CI providers, Cypress can't map the system environment variables required to link back to builds or pull requests. In this case we provide users some environment variables to help pass that information along. * Pull Request Id: `CYPRESS_PULL_REQUEST_ID` * Pull Request URL: `CYPRESS_PULL_REQUEST_URL` * Build URL: `CYPRESS_CI_BUILD_URL` Setting these will allow links within the Cloud run to take you to the appropriate place. #### Custom Environment Variables You can also set custom values for use in your tests. Which API you use depends on whether the value is sensitive. **_Private values: `cy.env()`_** For secrets like API tokens, set an OS environment variable prefixed with `CYPRESS_` using your CI provider's secret or masked variable settings: ``` export CYPRESS_SERVICE_API_TOKEN=secret-token-123 ``` Cypress strips the prefix, and your tests read the value with [`cy.env()`](/llm/markdown/api/commands/env.md), which retrieves only the values you request without serializing them into browser state: ``` cy.env(['SERVICE_API_TOKEN']).then(({ SERVICE_API_TOKEN }) => { cy.request({ url: 'https://api.example.com/users', headers: { Authorization: `Bearer ${SERVICE_API_TOKEN}` }, }) }) ``` **_Public values: `Cypress.expose()`_** For non-sensitive configuration that is safe to appear in browser state, like feature flags or API versions, pass values with the `--expose` CLI flag: * npm * Yarn * pnpm * Bun ``` npx cypress run --expose apiVersion=v2,featureFlag=true ``` ``` yarn cypress run --expose apiVersion=v2,featureFlag=true ``` ``` pnpm cypress run --expose apiVersion=v2,featureFlag=true ``` ``` bunx cypress run --expose apiVersion=v2,featureFlag=true ``` and read them synchronously with [`Cypress.expose()`](/llm/markdown/api/cypress-api/expose.md): ``` const apiVersion = Cypress.expose('apiVersion') // => 'v2' ``` Refer to the dedicated [Environment Variables & Secrets guide](/llm/markdown/app/guides/environment-variables.md) for more examples. ### Module API Oftentimes it can be less complex to programmatically control and boot your servers with a Node script. If you're using our [Module API](/llm/markdown/app/references/module-api.md) then you can write a script that boots and then shuts down the server later. As a bonus, you can work with the results and do other things. scripts/run-cypress-tests.js ``` const cypress = require('cypress') const server = require('./lib/my-server') ;(async () => { await server.start() // kick off a cypress run const results = await cypress.run() // stop your server when it's complete await server.stop() })() ``` ``` node scripts/run-cypress-tests.js ``` ## Common problems and solutions ### Missing binary When npm or yarn install the `cypress` package, a `postinstall` hook is executed that downloads the platform-specific Cypress binary. If the hook is skipped for any reason the Cypress binary will be missing (unless it was already cached). To better diagnose the error, add [commands to get information about the Cypress cache](/llm/markdown/app/references/command-line.md#cypress-cache-command) to your CI setup. This will print where the binary is located and what versions are already present. * npm * Yarn * pnpm * Bun ``` npx cypress cache path npx cypress cache list ``` ``` yarn cypress cache path yarn cypress cache list ``` ``` pnpm cypress cache path pnpm cypress cache list ``` ``` bunx cypress cache path bunx cypress cache list ``` If the required binary version is not found in the cache, you can try the following: 1. Clean your CI's cache using your CI's settings to force a clean `npm install` on the next build. 2. Run the binary install yourself by adding the command `npx cypress install` to your CI script. If there is a binary already present, it should finish quickly. ### Xvfb When running on Linux, Cypress needs an X11 server; otherwise it spawns its own X11 server during the test run. When running several Cypress instances in parallel, the spawning of multiple X11 servers at once can cause problems for some of them. In this case, you can separately start a single X11 server and pass the server's address to each Cypress instance using the `DISPLAY` variable. First, spawn the X11 server in the background at some port, for example `:99`. If you have installed `xvfb` on Linux or if you are using one of our Docker images from [cypress-docker-images](https://github.com/cypress-io/cypress-docker-images), the tools below should be available. ``` Xvfb :99 & ``` Second, set the X11 address in a system environment variable ``` export DISPLAY=:99 ``` Start Cypress as usual * npm * Yarn * pnpm * Bun ``` npx cypress run ``` ``` yarn cypress run ``` ``` pnpm cypress run ``` ``` bunx cypress run ``` After all tests across all Cypress instances finish, kill the Xvfb background process using `pkill` ``` pkill Xvfb ``` In certain Linux environments, you may experience connection errors with your X11 server. In this case, you may need to start Xvfb with the following command: ``` Xvfb -screen 0 1024x768x24 :99 & ``` Cypress internally passes these Xvfb arguments, but if you are spawning your own Xvfb, you would need to pass these arguments. This is necessary to avoid using 8-bit color depth with Xvfb, which will prevent Chrome or Electron from crashing. ### Colors If you want colors to be disabled, you can pass the `NO_COLOR` environment variable to disable colors. You may want to do this if ASCII characters or colors are not properly formatted in your CI. ``` NO_COLOR=1 cypress run ``` ## See also * [Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app) runs parallelized CI jobs across multiple operating systems, browsers, and viewport sizes. * [cypress-example-kitchensink](https://github.com/cypress-io/cypress-example-kitchensink#ci-status) is set up to run on multiple CI providers. * [Command Line](/llm/markdown/app/references/command-line.md) - all `cypress run` flags and options * [Test Replay](/llm/markdown/cloud/features/test-replay.md) * [Cross Browser Testing Guide](/llm/markdown/app/guides/cross-browser-testing.md) --- Source: https://docs.cypress.io/app/core-concepts/best-practices.md Section: Cypress App # Best Practices **Using an AI coding tool?** [Cypress AI Skills](/llm/markdown/app/tooling/ai-skills.md) encode these best practices directly into your AI tool so it applies them automatically — consistent selectors, proper waiting patterns, independent tests — without needing to prompt for them every time. The Cypress team maintains the [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app), a full stack example application that demonstrates **best practices and scalable strategies with Cypress in practical and realistic scenarios**. The RWA achieves full [code-coverage](/llm/markdown/app/tooling/code-coverage.md) with end-to-end tests [across multiple browsers](/llm/markdown/app/guides/cross-browser-testing.md) and [device sizes](/llm/markdown/api/commands/viewport.md), but also includes [visual regression tests](/llm/markdown/app/tooling/visual-testing.md), API tests, unit tests, and runs them all in an [efficient CI pipeline](https://cloud.cypress.io/projects/7s5okt). The app is bundled with everything you need, [just clone the repository](https://github.com/cypress-io/cypress-realworld-app) and start testing. ## Handling Secrets and Sensitive Data **Anti-Pattern:** Hardcoding secrets in test files, using `Cypress.expose()` for sensitive values, or exposing secrets to the browser context. **Best Practice:** Use the right API for the right purpose: `cy.env()` for sensitive values in tests and `Cypress.expose()` only for public configuration. When writing Cypress tests, you'll often need to work with sensitive data like API keys, passwords, tokens, or database credentials. Cypress provides three different APIs for handling configuration values, each with different security characteristics and use cases. ### Understanding the Security Model Cypress runs in two contexts: * **Browser context**: Where your tests execute and can access browser APIs. Values here are visible to application code, browser extensions, and developer tools. * **Node.js context**: Where plugins and tasks run. Values here never enter the browser and remain secure. | API | Context | Browser Exposure | Use Case | | --- | --- | --- | --- | | [`cy.env()`](/llm/markdown/api/commands/env.md) | Browser (privileged) | No | Sensitive values needed in tests | | [`Cypress.expose()`](/llm/markdown/api/cypress-api/expose.md) | Browser (public) | Yes | Public/non-sensitive configuration | ### Use `cy.env()` for Sensitive Values in Tests `cy.env()` is the recommended way to access sensitive environment variables in your tests. It provides privileged access that doesn't automatically expose values to the browser context. **When to use:** * API keys, passwords, tokens, or credentials needed in test code * Values that should remain secure but are needed within Cypress command chains **Example: Using API keys securely** ``` // ✅ Good: Using cy.env() for sensitive API key cy.env(['apiKey']).then(({ apiKey }) => { cy.request({ method: 'POST', url: 'https://api.example.com/data', headers: { Authorization: `Bearer ${apiKey}`, }, }) }) ``` **Configuration:** Set sensitive values via environment variables or configuration files: ``` // cypress.config.js { env: { apiKey: process.env.API_KEY, // From CI environment dbPassword: process.env.DB_PASSWORD, }, } ``` ### Use `Cypress.expose()` for public configuration `Cypress.expose()` is designed for **public, non-sensitive** configuration values that are safe to expose in the browser context. **When to use:** * Feature flags, API versions, environment labels * Plugin configuration that's safe to appear in browser state * Values needed synchronously outside of Cypress command chains * Configuration that application code might need to access **When NOT to use:** * ❌ API keys, passwords, tokens, or any credentials * ❌ Database connection strings * ❌ Any value that should remain secret **Example: Public configuration only** ``` // ✅ Good: Public configuration const apiVersion = Cypress.expose('apiVersion') // e.g., "v2" const featureFlag = Cypress.expose('featureFlag') // e.g., true // ❌ Bad: Never use for secrets const apiKey = Cypress.expose('apiKey') // ⚠️ Exposed to browser! ``` ### Security Best Practices 1. **Never hardcode secrets in test files** ``` // ❌ Bad: Hardcoded secret cy.request({ url: 'https://api.example.com/data', headers: { Authorization: 'Bearer secret-key-12345' }, }) // ✅ Good: From environment cy.env(['apiKey']).then(({ apiKey }) => { cy.request({ url: 'https://api.example.com/data', headers: { Authorization: `Bearer ${apiKey}` }, }) }) ``` 1. **Use environment variables in CI/CD** Set secrets in your CI/CD platform's secret management system: ``` # GitHub Actions example env: CYPRESS_API_KEY: ${{ secrets.API_KEY }} CYPRESS_DB_PASSWORD: ${{ secrets.DB_PASSWORD }} ``` 1. **Don't commit secrets to version control** Use `.gitignore` for files containing secrets: ``` # .gitignore cypress.env.json .env *.secret ``` 1. **Use `cy.env()` with explicit variable names** Only request the variables you need: ``` // ✅ Good: Explicit, minimal exposure cy.env(['apiKey']).then(({ apiKey }) => { // Use apiKey }) // ⚠️ Less ideal: Requesting more than needed cy.env(['apiKey', 'dbPassword', 'otherSecret']).then((env) => { // All values now in scope }) ``` ## Organizing Tests, Logging In, Controlling State **Anti-Pattern:** Sharing page objects, using your UI to log in, and not taking shortcuts. **Best Practice:** Test specs in isolation, programmatically log into your application, and take control of your application's state. We gave a "Best Practices" conference talk at AssertJS (February 2018). This video demonstrates how to approach breaking down your application and organizing your tests. [AssertJS - Cypress Best Practices](https://www.youtube.com/watch?v=5XQOK0v_YRE) Isolation applies to how you divide specs up, too. Organize specs around features and user flows rather than mirroring your application's page structure, and let spec duration and differing setup tell you when to split one up. See [How much to put in one spec file](/llm/markdown/app/core-concepts/writing-and-organizing-tests.md#How-much-to-put-in-one-spec-file). We have several [Logging in recipes](https://github.com/cypress-io/cypress-example-recipes#logging-in-recipes) in our examples. ## Selecting Elements **Anti-Pattern:** Using highly brittle selectors that are subject to change. **Best Practice:** Use `data-*` attributes to provide context to your selectors and isolate them from CSS or JS changes. Every test you write will include selectors for elements. To save yourself a lot of headaches, you should write selectors that are resilient to changes. Oftentimes we see users run into problems targeting their elements because: * Your application may use dynamic classes or ID's that change * Your selectors break from development changes to CSS styles or JS behavior Luckily, it is possible to avoid both of these problems. 1. Don't target elements based on CSS attributes such as: `id`, `class`, `tag` 2. Don't target elements that may change their `textContent` 3. Add `data-*` attributes to make it easier to target elements The [`cypress/require-data-selectors`](https://github.com/cypress-io/eslint-plugin-cypress#rules) rule in [`eslint-plugin-cypress`](https://github.com/cypress-io/eslint-plugin-cypress) can enforce `data-*` selectors automatically at lint time. ### How It Works Given a button that we want to interact with: ``` ``` Let's investigate how we could target it: | Selector | Recommended | Notes | | --- | --- | --- | | `cy.get('button').click()` | Never | Worst - too generic, no context. | | `cy.get('.btn.btn-large').click()` | Never | Bad. Coupled to styling. Highly subject to change. | | `cy.get('#main').click()` | Sparingly | Better. But still coupled to styling or JS event listeners. | | `cy.get('[name="submission"]').click()` | Sparingly | Coupled to the `name` attribute which has HTML semantics. | | `cy.contains('Submit').click()` | Depends | Much better. But still coupled to text content that may change. | | `cy.get('[data-cy="submit"]').click()` | Always | Best. Isolated from all changes. | Targeting the element above by `tag`, `class` or `id` is very volatile and highly subject to change. You may swap out the element, you may refactor CSS and update ID's, or you may add or remove classes that affect the style of the element. Instead, adding the `data-cy` attribute to the element gives us a targeted selector that's only used for testing. The `data-cy` attribute will not change from CSS style or JS behavioral changes, meaning it's not coupled to the **behavior** or **styling** of an element. Additionally, it makes it clear to everyone that this element is used directly by test code. #### Real World Example The [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app) uses two useful custom commands for selecting elements for testing: * `getBySel` yields elements with a `data-test` attribute that **match** a specified selector. * `getBySelLike` yields elements with a `data-test` attribute that **contains** a specified selector. cypress/support/commands.ts ``` declare global { namespace Cypress { interface Chainable { getBySel(selector: string, ...args: any[]): Chainable> getBySelLike( selector: string, ...args: any[] ): Chainable> } } } Cypress.Commands.add('getBySel', (selector, ...args) => { return cy.get(`[data-test=${selector}]`, ...args) }) Cypress.Commands.add('getBySelLike', (selector, ...args) => { return cy.get(`[data-test*=${selector}]`, ...args) }) ``` _Source: [cypress/support/commands.ts](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/support/commands.ts)_ ### Text Content After reading the above rules you may be wondering: > If I should always use data attributes, then when should I use `cy.contains()`? A rule of thumb is to ask yourself this: If the content of the element **changed** would you want the test to fail? * If the answer is yes: then use [`cy.contains()`](/llm/markdown/api/commands/contains.md) * If the answer is no: then use a data attribute. **Example:** If we looked at the `` of our button again... ``` ``` The question is: how important is the `Submit` text content to your test? If the text changed from `Submit` to `Save` - would you want the test to fail? If the answer is **yes** because the word `Submit` is critical and should not be changed - then use [`cy.contains()`](/llm/markdown/api/commands/contains.md) to target the element. This way, if it is changed, the test will fail. If the answer is **no** because the text could be changed - then use [`cy.get()`](/llm/markdown/api/commands/get.md) with data attributes. Changing the text to `Save` would then not cause a test failure. ### Cypress and Testing Library Cypress loves the Testing Library project. We use Testing Library internally, and our philosophy aligns closely with Testing Library's ethos and approach to writing tests. We strongly endorse their best practices for situations where, as with `cy.contains()`, you want to fail a test if a specific piece of content or accessible role is not present. You can use the [Cypress Testing Library](https://testing-library.com/docs/cypress-testing-library/intro/) package to use the familiar testing library methods (like `findByRole`, `findByLabelText`, etc...) to select elements in Cypress specs. If you are coming from a React Testing Library background and looking for more resources to understand how we recommend you approach testing your components, look to: [Cypress Component Testing](/llm/markdown/app/component-testing/get-started.md). ### Accessibility Testing Selecting elements with data attributes, text content, or Testing Library locators can each have some different implications for accessibility, but none of these approaches is a "complete" accessibility test, and you will always need additional, accessibility-specific testing (including automated and manual tests) to confirm your application is working as expected for people with disabilities and the technology they use. See [our accessibility testing guide](/llm/markdown/app/guides/accessibility-testing.md) for more details and comparisons of approaches. ## Assigning Return Values **Anti-Pattern:** Trying to assign the return value of Commands with `const`, `let`, or `var`. **Best Practice:** Use [aliases and closures to access and store](/llm/markdown/app/core-concepts/variables-and-aliases.md) what Commands yield you. The [`cypress/no-assigning-return-values`](https://github.com/cypress-io/eslint-plugin-cypress#rules) rule in [`eslint-plugin-cypress`](https://github.com/cypress-io/eslint-plugin-cypress) flags this pattern automatically at lint time. Many first time users look at Cypress code and think it runs synchronously. We see new users commonly write code that looks like this: ``` // DONT DO THIS. IT DOES NOT WORK // THE WAY YOU THINK IT DOES. const a = cy.get('a') cy.visit('https://example.cypress.io') // nope, fails a.first().click() // Instead, do this. cy.get('a').as('links') cy.get('@links').first().click() ``` **Did you know?** You rarely have to ever use `const`, `let`, or `var` in Cypress. If you're using them, you will want to do some refactoring. If you are new to Cypress and wanting to better understand how Commands work - [please read our Introduction to Cypress guide](/llm/markdown/app/core-concepts/introduction-to-cypress.md#Chains-of-Commands). If you're familiar with Cypress commands already, but find yourself using `const`, `let`, or `var` then you're typically trying to do one of two things: * You're trying to **store and compare** values such as **text**, **classes**, **attributes**. * You're trying to share **values** between tests and hooks like `before` and `beforeEach`. For working with either of these patterns, please read our [Variables and Aliases guide](/llm/markdown/app/core-concepts/variables-and-aliases.md). ## Visiting External Sites **Anti-Pattern:** Trying to visit or interact with sites or servers you do not control. **Best Practice:** Only test websites that you control. Try to avoid visiting or requiring a 3rd party server. If you choose, you may use [`cy.request()`](/llm/markdown/api/commands/request.md) to talk to 3rd party servers via their APIs. If possible, cache results via [`cy.session()`](/llm/markdown/api/commands/session.md) to avoid repeat visits. See also reasons against [Testing Apps You Don't Control](/llm/markdown/app/end-to-end-testing/testing-your-app.md#Testing-Apps-You-Dont-Control). One of the first things many of our users attempt to do is involve 3rd party servers or services in their tests. You may want to access 3rd party services in several situations: 1. Testing log in when your app uses another provider via OAuth. 2. Verifying your server updates a 3rd party server. 3. Checking your email to see if your server sent a "forgot password" email. If you choose, these situations can be tested with [`cy.visit()`](/llm/markdown/api/commands/visit.md) and [`cy.origin()`](/llm/markdown/api/commands/origin.md). However, you will only want to utilize these commands for resources in your control, either by controlling the domain or hosted instance. These use cases are common for: * Authentication as a service platforms, such as Auth0, Okta, Microsoft, AWS Cognito, and others via username/password authentication. These domains and service instances are usually owned and controlled by you or your organization. * CMS instances, such as a Contentful or Wordpress instance. * Other types of services under a domain in which you control. ### Potential Challenges Authenticating with Social Platforms Other services, such as social logins through popular media providers, are not recommended. Testing social logins may work, especially if run locally. However, we consider this a bad practice and do not recommend it because: * It's incredibly time consuming and slows down your tests (unless using [`cy.session()`](/llm/markdown/api/commands/session.md)). * The 3rd party site may have changed or updated its content. * The 3rd party site may be having issues outside of your control. * The 3rd party site may detect you are testing via a script and block you. * The 3rd party site might have policies against automated login, leading to banning of accounts. * The 3rd party site might detect you are a bot, and provide mechanisms such as two-factor authentication, captchas, and other means to prevent automation. This is common with continuous integration platforms and general automation. * The 3rd party site may be running A/B campaigns. Let's look at a few strategies for dealing with these situations. ### When logging in Many OAuth providers, especially social logins, run A/B experiments, which means that their login screen is dynamically changing. This makes automated testing difficult. Many OAuth providers also throttle the number of web requests you can make to them. For instance, if you try to test Google, Google will **automatically** detect that you are not a human and instead of giving you an OAuth login screen, they will make you fill out a captcha. Additionally, testing through an OAuth provider is mutable - you will first need a real user on their service and then modifying anything on that user might affect other tests downstream. **Here are solutions you may choose to use to alleviate these problems:** 1. Use another platform that you control to log in with username and password via [`cy.origin()`](/llm/markdown/api/commands/origin.md). This likely guarantees that you will not run into the problems listed above, while still being able to automate your login flow. You can reduce the amount of authentication requests by utilizing [`cy.session()`](/llm/markdown/api/commands/session.md). 2. [Stub](/llm/markdown/api/commands/stub.md) out the OAuth provider and bypass it using their UI altogether if [`cy.origin()`](/llm/markdown/api/commands/origin.md) is not an option. You could trick your application into believing the OAuth provider has passed its token to your application. 3. If you **must** get a real token and [`cy.origin()`](/llm/markdown/api/commands/origin.md) is not an option, you can use [`cy.request()`](/llm/markdown/api/commands/request.md) and use the **programmatic** API that your OAuth provider provides. These APIs likely change **more** infrequently and you avoid problems like throttling and A/B campaigns. 4. Instead of having your test code bypass OAuth, you could also ask your server for help. Perhaps all an OAuth token does is generate a user in your database. Oftentimes OAuth is only useful initially and your server establishes its own session with the client. If that is the case, use [`cy.request()`](/llm/markdown/api/commands/request.md) to get the session directly from your server and bypass the provider altogether if [`cy.origin()`](/llm/markdown/api/commands/origin.md) is not an option. **Recipes** [We have several examples of doing this in our logging in recipes.](/llm/markdown/app/references/recipes.md) ### 3rd party servers Sometimes actions that you take in your application **may** affect another 3rd party application. These situations are not that common, but it is possible. Imagine your application integrates with GitHub and by using your application you can change data inside of GitHub. After running your test, instead of trying to [`cy.visit()`](/llm/markdown/api/commands/visit.md) GitHub, you can use [`cy.request()`](/llm/markdown/api/commands/request.md) to programmatically interact with GitHub's APIs directly. This avoids ever needing to touch the UI of another application. ### Verifying sent emails Typically, when going through scenarios like user registration or forgotten passwords, your server schedules an email to be delivered. 1. If your application is running locally and is sending the emails directly through an SMTP server, you can use a temporary local test SMTP server running inside Cypress. Read the blog post ["Testing HTML Emails using Cypress"](https://www.cypress.io/blog/2021/05/11/testing-html-emails-using-cypress/) for details. 2. If your application is using a 3rd party email service, or you cannot stub the SMTP requests, you can use a test email inbox with an API access. Read the blog post ["Full Testing of HTML Emails using SendGrid and Ethereal Accounts"](https://www.cypress.io/blog/2021/05/24/full-testing-of-html-emails-using-ethereal-accounts/) for details. Cypress can even load the received HTML email in its browser to verify the email's functionality and visual style: 1. In other cases, you should try using [`cy.request()`](/llm/markdown/api/commands/request.md) command to query the endpoint on your server that tells you what email has been queued or delivered. That would give you a programmatic way to know without involving the UI. Your server would have to expose this endpoint. 2. You could also use `cy.request()` to a 3rd party email recipient server that exposes an API to read off emails. You will then need the proper authentication credentials, which your server could provide via [`cy.task()`](/llm/markdown/api/commands/task.md). Some email services already provide [Cypress plugins](/llm/markdown/app/plugins/plugins-list.md#email) to access emails. ## Having Tests Rely On The State Of Previous Tests **Anti-Pattern:** Coupling multiple tests together. **Best Practice:** Tests should always be able to be run independently from one another **and still pass**. You only need to do one thing to know whether you've coupled your tests incorrectly, or if one test is relying on the state of a previous one. Change `it` to [`it.only`](https://jestjs.io/docs/api#testonlyname-fn-timeout) on the test and refresh the browser. If this test can run **by itself** and pass - congratulations you have written a good test. If this is not the case, then you should refactor and change your approach. How to solve this: * Move repeated code in previous tests to `before` or `beforeEach` hooks. * Combine multiple tests into one larger test. Let's imagine the following test that is filling out the form. * End-to-End Test * Component Test ``` // an example of what NOT TO DO describe('my form', () => { it('visits the form', () => { cy.visit('/users/new') }) it('requires first name', () => { cy.get('[data-testid="first-name"]').type('Johnny') }) it('requires last name', () => { cy.get('[data-testid="last-name"]').type('Appleseed') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) ``` ``` // an example of what NOT TO DO describe('my form', () => { it('visits the form', () => { cy.mount() }) it('requires first name', () => { cy.get('[data-testid="first-name"]').type('Johnny') }) it('requires last name', () => { cy.get('[data-testid="last-name"]').type('Appleseed') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) ``` What's wrong with the above tests? They are all coupled together! If you were to change `it` to [`it.only`](https://jestjs.io/docs/api#testonlyname-fn-timeout) on any of the last three tests, they would fail. Each test requires the previous to run in a specific order in order to pass. Here's 2 ways we can fix this: ### 1\. Combine into one test * End-to-End Test * Component Test ``` // a bit better describe('my form', () => { it('can submit a valid form', () => { cy.visit('/users/new') cy.log('filling out first name') // if you really need this cy.get('[data-testid="first-name"]').type('Johnny') cy.log('filling out last name') // if you really need this cy.get('[data-testid="last-name"]').type('Appleseed') cy.log('submitting form') // if you really need this cy.get('form').submit() }) }) ``` ``` // a bit better describe('my form', () => { it('can submit a valid form', () => { cy.mount() cy.log('filling out first name') // if you really need this cy.get('[data-testid="first-name"]').type('Johnny') cy.log('filling out last name') // if you really need this cy.get('[data-testid="last-name"]').type('Appleseed') cy.log('submitting form') // if you really need this cy.get('form').submit() }) }) ``` Now we can put an `.only` on this test and it will run successfully irrespective of any other test. The ideal Cypress workflow is writing and iterating on a single test at a time. ### 2\. Run shared code before each test * End-to-End Test * Component Test ``` describe('my form', () => { beforeEach(() => { cy.visit('/users/new') cy.get('[data-testid="first-name"]').type('Johnny') cy.get('[data-testid="last-name"]').type('Appleseed') }) it('displays form validation', () => { // clear out first name cy.get('[data-testid="first-name"]').clear() cy.get('form').submit() cy.get('[data-testid="errors"]').should('contain', 'First name is required') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) ``` ``` describe('my form', () => { beforeEach(() => { cy.mount() cy.get('[data-testid="first-name"]').type('Johnny') cy.get('[data-testid="last-name"]').type('Appleseed') }) it('displays form validation', () => { // clear out first name cy.get('[data-testid="first-name"]').clear() cy.get('form').submit() cy.get('[data-testid="errors"]').should('contain', 'First name is required') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) ``` This above example is ideal because now we are resetting the state between each test and ensuring nothing in previous tests leaks into subsequent ones. We're also paving the way to make it less complicated to write multiple tests against the "default" state of the form. That way each test stays lean but each can be run independently and pass. ## Creating "Tiny" Tests With A Single Assertion[ End-to-End Only ](/llm/markdown/app/core-concepts/testing-types.md#What-is-E2E-Testing) **Anti-Pattern:** Acting like you're writing unit tests. **Best Practice:** Add multiple assertions and don't worry about it We've seen many users writing this kind of code: ``` describe('my form', () => { beforeEach(() => { cy.visit('/users/new') cy.get('[data-testid="first-name"]').type('johnny') }) it('has validation attr', () => { cy.get('[data-testid="first-name"]').should( 'have.attr', 'data-validation', 'required' ) }) it('has active class', () => { cy.get('[data-testid="first-name"]').should('have.class', 'active') }) it('has formatted first name', () => { cy.get('[data-testid="first-name"]') // capitalized first letter .should('have.value', 'Johnny') }) }) ``` While technically this runs fine - this is really excessive, and not performant. Why you do this pattern in component and unit tests: * When assertions failed you relied on the test's title to know what failed * You were told that adding multiple assertions was bad and accepted this as truth * There was no performance penalty splitting up multiple tests because they run really fast Why you shouldn't do this in end-to-end tests: * Writing integration tests is not the same as unit tests * You will always know (and can visually see) which assertion failed in a large test * Cypress runs a series of async lifecycle events that reset state between tests * Resetting tests is much slower than adding more assertions It is common for tests in Cypress to issue 30+ commands. Because nearly every command has an implicit assertion (and can therefore fail), even by limiting your assertions you're not saving yourself anything because **any single command could implicitly fail**. How you should rewrite those tests: ``` describe('my form', () => { beforeEach(() => { cy.visit('/users/new') }) it('validates and formats first name', () => { cy.get('[data-testid="first-name"]') .type('johnny') .should('have.attr', 'data-validation', 'required') .and('have.class', 'active') .and('have.value', 'Johnny') }) }) ``` ### Where the balance sits Grouping assertions has a limit in the other direction. A single test that walks an entire checkout flow (browse, add to cart, apply a coupon, enter shipping, pay, see the confirmation) reports one failure under one title, and a title like "completes checkout" tells the person debugging almost nothing about which part of the application broke. They have to open the run to find out. The most useful way to think about where a test starts and stops is the signal its failure sends. Both extremes weaken that signal: * A test large enough that its title no longer describes what failed hands the diagnosis back to whoever is debugging. * Tests small enough that they all lean on the same lengthy setup fail together the moment that setup breaks, and a screen of red tells you only that something upstream went wrong. Aim for the middle: each test covers a meaningful piece of behavior, its title names that behavior, and a failure narrows down where to look. For the checkout example, that might be one test for adding an item to the cart, one for applying a coupon, and one for completing payment, each getting to its starting point [programmatically](#Having-Tests-Rely-On-The-State-Of-Previous-Tests) rather than by repeating the steps before it through the UI. ## Using `after` Or `afterEach` Hooks **Anti-Pattern:** Using `after` or `afterEach` hooks to clean up state. **Best Practice:** Clean up state **before** tests run. We see many of our users adding code to an `after` or `afterEach` hook in order to clean up the state generated by the current test(s). We most often see test code that looks like this: ``` describe('logged in user', () => { beforeEach(() => { cy.login() }) afterEach(() => { cy.logout() }) it('tests', ...) it('more', ...) it('things', ...) }) ``` Let's look at why this is not really necessary. ### Dangling state is your friend One of the **best** parts of Cypress is its emphasis on debuggability. Unlike other testing tools - when your tests end - you are left with your working application at the exact point where your test finished. This is an **excellent** opportunity for you to **use** your application in the state the tests finished! This enables you to write **partial tests** that drive your application step by step, writing your test and application code at the same time. We have built Cypress to support this use case. In fact, Cypress **does not** clean up its own internal state when the test ends. We **want** you to have dangling state at the end of the test! Things like [stubs](/llm/markdown/api/commands/stub.md), [spies](/llm/markdown/api/commands/spy.md), even [intercepts](/llm/markdown/api/commands/intercept.md) are **not** removed at the end of the test. This means your application will behave identically while it is running Cypress commands or when you manually work with it after a test ends. If you remove your application's state after each test, then you instantly lose the ability to use your application in this mode. Logging out at the end would always leave you with the same login page at the end of the test. In order to debug your application or write a partial test, you would always be left commenting out your custom `cy.logout()` command. ### It's all downside with no upside For the moment, let's assume that for some reason your application desperately **needs** that last bit of `after` or `afterEach` code to run. Let's assume that if that code is not run - all is lost. That is fine - but even if this is the case, it should not go in an `after` or `afterEach` hook. Why? So far we have been talking about logging out, but let's use a different example. Let's use the pattern of needing to reset your database. **The idea goes like this:** > After each test I want to ensure the database is reset back to 0 records so when the next test runs, it is run with a clean state. **With that in mind you write something like this:** ``` afterEach(() => { cy.resetDb() }) ``` Here is the problem: **there is no guarantee that this code will run.** If, hypothetically, you have written this command because it **has** to run before the next test does, then the absolute **worst place** to put it is in an `after` or `afterEach` hook. Why? Because if you refresh Cypress in the middle of the test - you will have built up partial state in the database, and your custom `cy.resetDb()` function **will never get called**. If this state cleanup is **truly** required, then the next test will instantly fail. Why? Because resetting the state never happened when you refreshed Cypress. ### State reset should go before each test The simplest solution here is to move your reset code to **before** the test runs. Code put in a `before` or `beforeEach` hook will **always** run prior to the test - even if you refreshed Cypress in the middle of an existing one! This is also a great opportunity to use [root level hooks in mocha](https://github.com/mochajs/mochajs.github.io/blob/master/index.md#root-level-hooks). A great place to put this configuration is in the [supportFile](/llm/markdown/app/core-concepts/writing-and-organizing-tests.md#Support-file), since it is loaded before any test files are evaluated. **Hooks you add to the root will always run on all suites!** ``` // cypress/support/e2e.js or cypress/support/component.js beforeEach(() => { // now this runs prior to every test // across all files no matter what cy.resetDb() }) ``` ### Is resetting the state necessary? One final question you should ask yourself is - is resetting the state even necessary? Remember, Cypress already automatically enforces [test isolation](/llm/markdown/app/core-concepts/writing-and-organizing-tests.md#Test-Isolation) by clearing state before each test. Make sure you are not trying to clean up state that is already cleaned up by Cypress automatically. If the state you are trying to clean lives on the server - by all means, clean that state. You will need to run these types of routines! But if the state is related to your application currently under test - you likely do not even need to clear it. The only times you **ever** need to clean up state, is if the operations that one test runs affects another test downstream. In only those cases do you need state cleanup. #### Real World Example The [Real World App (RWA)](https://github.com/cypress-io/cypress-realworld-app) resets and re-seeds its database via a custom [Cypress task](/llm/markdown/api/commands/task.md) called `db:seed` in a `beforeEach` hook. This allows each test to start from a clean slate and a deterministic state. For example: ``` // cypress/tests/ui/auth.cy.ts beforeEach(function () { cy.task('db:seed') // ... }) ``` _Source: [cypress/tests/ui/auth.cy.ts](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui/auth.spec.ts)_ The `db:seed` task is defined within the [setupNodeEvents](/llm/markdown/app/plugins/plugins-guide.md#Using-a-plugin) function of the project, and in this case sends a request to a dedicated back end API of the app to appropriately re-seed the database. * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ // setupNodeEvents can be defined in either // the e2e or component configuration e2e: { setupNodeEvents(on, config) { on('task', { async 'db:seed'() { // Send request to backend API to re-seed database with test data const { data } = await axios.post(`${testDataApiEndpoint}/seed`) return data }, //... }) }, }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ // setupNodeEvents can be defined in either // the e2e or component configuration e2e: { setupNodeEvents(on, config) { on('task', { async 'db:seed'() { // Send request to backend API to re-seed database with test data const { data } = await axios.post(`${testDataApiEndpoint}/seed`) return data }, //... }) }, }, }) ``` _Source: [cypress/plugins/index.ts](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/plugins/index.ts)_ The same practice above can be used for any type of database (PostgreSQL, MongoDB, etc.). In this example, a request is sent to a back end API, but you could also interact directly with your database with direct queries, custom libraries, etc. If you already have non-JavaScript methods of handling or interacting with your database, you can use [`cy.task`](/llm/markdown/api/commands/task.md) to run them from Node, including [spawning an external command or CLI](/llm/markdown/api/commands/task.md#Run-an-external-command-or-CLI). ## Unnecessary Waiting **Anti-Pattern:** Waiting for arbitrary time periods using [`cy.wait(Number)`](/llm/markdown/api/commands/wait.md#Time). **Best Practice:** Use route aliases or assertions to guard Cypress from proceeding until an explicit condition is met. In Cypress, you almost never need to use `cy.wait()` for an arbitrary amount of time. If you are finding yourself doing this, there is likely a much simpler way. The [`cypress/no-unnecessary-waiting`](https://github.com/cypress-io/eslint-plugin-cypress#rules) rule in [`eslint-plugin-cypress`](https://github.com/cypress-io/eslint-plugin-cypress) flags `cy.wait()` at lint time, before tests ever run. Let's imagine the following examples: ### Unnecessary wait for `cy.request()` Waiting here is unnecessary since the [`cy.request()`](/llm/markdown/api/commands/request.md) command will not resolve until it receives a response from your server. Adding the wait here only adds 5 seconds after the [`cy.request()`](/llm/markdown/api/commands/request.md) has already resolved. ``` cy.request('http://localhost:8080/db/seed') cy.wait(5000) // <--- this is unnecessary ``` ### Unnecessary wait for `cy.visit()`[ End-to-End Only ](/llm/markdown/app/core-concepts/testing-types.md#What-is-E2E-Testing) Waiting for this is unnecessary because the [cy.visit()](/llm/markdown/api/commands/visit.md) resolves once the page fires its `load` event. By that time all of your assets have been loaded including javascript, stylesheets, and html. ``` cy.visit('http://localhost/8080') cy.wait(5000) // <--- this is unnecessary ``` ### Unnecessary wait for `cy.get()` Waiting for the [`cy.get()`](/llm/markdown/api/commands/get.md) below is unnecessary because [`cy.get()`](/llm/markdown/api/commands/get.md) automatically retries until the table's `tr` has a length of 2. Whenever commands have an assertion they will not resolve until their associated assertions pass. This enables you to describe the state of your application without having to worry about when it gets there. ``` cy.intercept('GET', '/users', [{ name: 'Maggy' }, { name: 'Joan' }]) cy.get('#fetch').click() cy.wait(4000) // <--- this is unnecessary cy.get('table tr').should('have.length', 2) ``` Alternatively a better solution to this problem is by waiting explicitly for an aliased route. ``` cy.intercept('GET', '/users', [{ name: 'Maggy' }, { name: 'Joan' }]).as( 'getUsers' ) cy.get('[data-testid="fetch-users"]').click() cy.wait('@getUsers') // <--- wait explicitly for this route to finish cy.get('table tr').should('have.length', 2) ``` ## Running Tests Intelligently As your test suite grows and takes longer to run, you may find yourself hitting performance bottlenecks on your CI system. We recommend integrating your source control system with your test suite such that merges are blocked until all your Cypress tests have passed. The downside of this is that longer test execution times slow the velocity at which branches may be merged and features may be shipped. This issue is compounded further if you have dependent chains of branches waiting to be merged. One solution to this problem is **Smart Orchestration** with Cypress Cloud. Using a combination of [parallelization](/llm/markdown/cloud/features/smart-orchestration/parallelization.md), [load balancing](/llm/markdown/cloud/features/smart-orchestration/load-balancing.md#Balance-strategy), [Auto Cancellation](/llm/markdown/cloud/features/smart-orchestration/run-cancellation.md), and [Spec Prioritization](/llm/markdown/cloud/features/smart-orchestration/spec-prioritization.md), Smart Orchestration maximizes your available compute resources & minimizes waste. ## Web Servers **Anti-Pattern:** Trying to start a web server from within Cypress scripts with [`cy.task()`](/llm/markdown/api/commands/task.md). **Best Practice:** Start a web server prior to running Cypress. We do NOT recommend trying to start your back end web server from within Cypress. Any command run by [cy.task()](/llm/markdown/api/commands/task.md) has to exit eventually. Otherwise, Cypress will not continue running any other commands. Trying to start a web server from [cy.task()](/llm/markdown/api/commands/task.md) causes all kinds of problems because: * You have to background the process * You lose access to it via terminal * You don't have access to its `stdout` or logs * Every time your tests run, you'd have to work out the complexity around starting an already running web server. * You would likely encounter constant port conflicts **Why can't I shut down the process in an `after` hook?** Because there is no guarantee that code running in an `after` will always run. While working in the Cypress Test Runner you can always restart / refresh while in the middle of a test. When that happens, code in an `after` won't execute. **What should I do then?** Start your web server before running Cypress and kill it after it completes. Are you trying to run in CI? We have [examples showing you how to start and stop your web server](/llm/markdown/app/continuous-integration/overview.md#Boot-your-server). ## Setting a Global `baseUrl` **Anti-Pattern:** Using [cy.visit()](/llm/markdown/api/commands/visit.md) without setting a `baseUrl`. **Best Practice:** Set a `baseUrl` in your [Cypress configuration](/llm/markdown/app/references/configuration.md). By adding a [baseUrl](/llm/markdown/app/references/configuration.md#Global) in your configuration Cypress will attempt to prefix the `baseUrl` any URL provided to commands like [cy.visit()](/llm/markdown/api/commands/visit.md) and [cy.request()](/llm/markdown/api/commands/request.md) that are not fully qualified domain name (FQDN) URLs. This allows you to omit hard-coding fully qualified domain name (FQDN) URLs in commands. For example, ``` cy.visit('http://localhost:8080/index.html') ``` can be shortened to ``` cy.visit('index.html') ``` Not only does this create tests that can easily switch between domains, i.e. running a dev server on `http://localhost:8080` vs a deployed production server domain, but adding a `baseUrl` can also save some time during the initial startup of your Cypress tests. When you start running your tests, Cypress does not know the url of the app you plan to test. So, Cypress initially opens on `https://localhost` + a random port. ### Without `baseUrl` set, Cypress loads main window in `localhost` + random port As soon as it encounters a [cy.visit()](/llm/markdown/api/commands/visit.md), Cypress then switches to the url of the main window to the url specified in your visit. This can result in a 'flash' or 'reload' when your tests first start. By setting the `baseUrl`, you can avoid this reload altogether. Cypress will load the main window in the `baseUrl` you specified as soon as your tests start. ### Cypress configuration file * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:8484', }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ e2e: { baseUrl: 'http://localhost:8484', }, }) ``` ### With `baseUrl` set, Cypress loads main window in `baseUrl` Having a `baseUrl` set gives you the added bonus of seeing an error if your server is not running during `cypress open` at the specified `baseUrl`. We also display an error if your server is not running at the specified `baseUrl` during `cypress run` after several retries. ### Usage of `baseUrl` in depth This [short video](https://www.youtube.com/watch?v=f5UaXuAc52c) explains in depth how to use `baseUrl` correctly. --- Source: https://docs.cypress.io/app/core-concepts/interacting-with-elements.md Section: Cypress App # Interacting with Elements **UI Coverage** turns your runs into a visual map of the interactive elements your tests exercise and the ones they miss, with no code changes or instrumentation. [Schedule a demo](https://www.cypress.io/ui-coverage?utm_medium=premium-solution-tip&utm_source=docs.cypress.io&utm_content=Schedule%20a%20demo). ## Visibility ### Default Behavior As of Cypress 16, the default visibility algorithm delegates to the browser's native [`Element.checkVisibility()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility) API. This is faster than the previous DOM-walking algorithm and aligns Cypress's notion of "visible" with the browser's own definition, making results more predictable across layouts. See [Visibility Strategy](#Visibility-Strategy) below to configure or opt back into the previous algorithm. #### An element is considered hidden if: * Its `width` or `height` is `0` (via the bounding rect zero-dimension guard). * Its computed CSS properties hide it (per `checkVisibility()`): * `display: none` on the element or any ancestor. * `visibility: hidden` or `visibility: collapse`. * `content-visibility: hidden`, or `content-visibility: auto` when not rendered. * `opacity: 0` (when asserting visibility — see Opacity note below). **Opacity** Elements where the CSS property (or ancestors) is `opacity: 0` are considered hidden when [asserting on the element's visibility directly](/llm/markdown/app/references/assertions.md#Visibility). However elements where the CSS property (or ancestors) is `opacity: 0` are considered actionable and any commands used to interact with the hidden element will perform the action. ### Visibility Strategy Configure which algorithm Cypress uses with the [`visibilityStrategy`](/llm/markdown/app/references/configuration.md#Actionability) option, which accepts either `'modern'` (the default, described in [Default Behavior](#Default-Behavior) above) or `'legacy'`. The legacy algorithm additionally considered elements hidden when they were: * Clipped by an ancestor's `overflow: hidden` * Scrolled out of view of an `overflow: auto`/`overflow: scroll` ancestor * Scaled to zero in one axis via a `transform` (e.g. `transform: scale(0)`) * Rotated past 90 degrees via `transform` with `backface-visibility: hidden` * Covered by another element while positioned `fixed` or `sticky` (legacy only checks coverage for fixed- or sticky-positioned elements via `document.elementFromPoint`) The modern algorithm intentionally does **not** detect these cases. If your tests rely on this legacy behavior, you can opt back into the previous algorithm: * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ visibilityStrategy: 'legacy', }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ visibilityStrategy: 'legacy', }) ``` `visibilityStrategy` can also be set per-suite or per-test: ``` describe('My Test Suite', { visibilityStrategy: 'legacy' }, () => { it('Requires legacy visibility to pass', () => { // ... }) }) ``` `visibilityStrategy` is deprecated. Both the `'legacy'` value and the option itself will be removed in a future major version of Cypress. Use it only as a temporary migration path while updating tests that depend on legacy visibility semantics. #### Updating tests that depended on the legacy algorithm When you encounter a test that fails under the modern algorithm, prefer updating the assertion to verify the same user-visible behavior in an algorithm-agnostic way rather than opting into `'legacy'`. **Scroll-clipped elements** The modern algorithm reports elements scrolled out of an `overflow: auto` ancestor as visible (they are technically rendered, just outside the scroll container's visible area). Assert on the geometry directly: ``` // Before cy.get('#scroll-container button').should('not.be.visible') // After cy.get('#scroll-container button').then(($el) => { const container = $el[0].closest('#scroll-container') expect($el[0].getBoundingClientRect().top).to.be.greaterThan( container.getBoundingClientRect().bottom ) }) ``` **Children inside a collapsed container that hides via `overflow: hidden` + `max-height: 0`** Many UI libraries hide collapsible content by setting `max-height: 0` and `overflow: hidden` on a wrapper. The wrapper itself reports as hidden under the modern algorithm (its bounding rect collapses to zero), but children with their own non-zero dimensions inside that wrapper are not detected as hidden — `checkVisibility()` doesn't account for ancestor clipping. Most libraries also set `aria-hidden="true"` on the closed wrapper — assert on that instead: ``` // Before cy.get('.collapsible-content .nested-item').should('not.be.visible') // After cy.get('.collapsible-content .nested-item') .closest('[aria-hidden]') .should('have.attr', 'aria-hidden', 'true') ``` **Text truncated with `text-overflow: ellipsis`** Truncated text is rendered with an ellipsis but the underlying text node is still considered visible by the browser. Assert that the truncating ancestor's `scrollWidth` exceeds its `clientWidth`: ``` cy.get('.truncate-container').then(($el) => { expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth) }) ``` ## Actionability Some commands in Cypress are for interacting with the DOM such as: * [`.click()`](/llm/markdown/api/commands/click.md) * [`.dblclick()`](/llm/markdown/api/commands/dblclick.md) * [`.rightclick()`](/llm/markdown/api/commands/rightclick.md) * [`.type()`](/llm/markdown/api/commands/type.md) * [`.clear()`](/llm/markdown/api/commands/clear.md) * [`.check()`](/llm/markdown/api/commands/check.md) * [`.uncheck()`](/llm/markdown/api/commands/uncheck.md) * [`.select()`](/llm/markdown/api/commands/select.md) * [`.trigger()`](/llm/markdown/api/commands/trigger.md) * [`.selectFile()`](/llm/markdown/api/commands/selectfile.md) We call these "action commands." These actions simulate a user interacting with your application. Under the hood, Cypress fires the events a browser would fire thus causing your application's event bindings to fire. Prior to issuing any of the commands, we check the current state of the DOM and take some actions to ensure the DOM element is "ready" to receive the action. Cypress will watch the DOM - re-running the queries that yielded the current subject - until an element passes all of these checks for the duration of the [`defaultCommandTimeout`](/llm/markdown/app/references/configuration.md#Timeouts) (described in depth in the [Implicit Assertions](/llm/markdown/app/core-concepts/introduction-to-cypress.md#Implicit-Assertions) core concept guide). **_Checks and Actions Performed_** * [Scroll the element into view.](#Scrolling) * [Ensure the element is not hidden.](#Visibility) * [Ensure the element is not disabled.](#Disability) * [Ensure the element is not detached.](#Detached) * [Ensure the element is not readonly.](#Readonly) * [Ensure the element is not animating.](#Animations) * [Ensure the element is not covered.](#Covering) * [Scroll the page if still covered by an element with fixed position.](#Scrolling) * [Fire the event at the desired coordinates.](#Coordinates) Whenever Cypress cannot interact with an element, it could fail at any of the above steps. You will usually get an error explaining why the element was not found to be actionable. ### Disability Cypress checks whether the `disabled` property is `true` on a [form control](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled) element, such as `button` or `input`. Setting a `disabled` attribute on other elements will have no effect on a user's ability to interact with them, and won't impact Cypress actionability checks. ### Detached Cypress checks whether an element you are making assertions on is still within the `document` of the application under test. When many applications rerender the DOM, they actually remove the DOM element and insert a new DOM element in its place with the newly change attributes. This is why it's important not to chain _action commands_ together - cypress can re-run queries to locate the fresh element, but it will [never re-run commands](/llm/markdown/app/core-concepts/retry-ability.md). ### Readonly Cypress checks whether an element's `readonly` property is set during [.type()](/llm/markdown/api/commands/type.md). ### Animations Cypress will automatically determine if an element is animating and wait until it stops. To calculate whether an element is animating we take a sample of the last positions it was at and calculate the element's slope. You might remember this from 8th grade algebra. 😉 To calculate whether an element is animating we check the current and previous positions of the element itself. If the distance exceeds the [`animationDistanceThreshold`](/llm/markdown/app/references/configuration.md#Actionability), then we consider the element to be animating. When coming up with this value, we did a few experiments to find a speed that "feels" too fast for a user to interact with. You can always [increase or decrease this threshold](/llm/markdown/app/references/configuration.md#Actionability). You can also turn off our checks for animations with the configuration option [`waitForAnimations`](/llm/markdown/app/references/configuration.md#Actionability). ### Covering We also ensure that the element we're attempting to interact with isn't covered by a parent element. For instance, an element could pass all of the previous checks, but a giant dialog could be covering the entire screen making interacting with the element impossible for any real user. When checking to see if the element is covered we always check its center coordinates. If a _child_ of the element is covering it - that's okay. In fact we'll automatically issue the events we fire to that child. Imagine you have a button: ``` ``` Oftentimes either the `` or `` element is covering the exact coordinate we're attempting to interact with. In those cases, the event fires on the child. We even note this for you in the [Command Log](/llm/markdown/app/core-concepts/open-mode.md#Command-Log). ### Scrolling Before interacting with an element, we will _always_ scroll it into view (including any of its parent containers). Even if the element was visible without scrolling, we perform the scrolling algorithm in order to reproduce the same behavior every time the command is run. This scrolling logic only applies to [commands that are actionable above](#Actionability). **We do not scroll elements** into view when using DOM commands such as [cy.get()](/llm/markdown/api/commands/get.md) or [.find()](/llm/markdown/api/commands/find.md). By default, the scrolling algorithm works by scrolling the top of the element we issued the command on to the top of its scrollable container. The horizontal position is left to the browser, which scrolls only as far as it needs to bring the element into view, so a container that already shows the element horizontally is not scrolled sideways. After scrolling the element, if we determine that it is still being covered up, we will continue to scroll and "nudge" the page until it becomes visible. This most frequently happens when you have `position: fixed` or `position: sticky` navigation elements which are fixed to the top of the page. Our algorithm _should_ always be able to scroll until the element is not covered. To change the position in the viewport to where we scroll an element, you can use the [`scrollBehavior`](/llm/markdown/app/references/configuration.md#Actionability) configuration option. This can be useful if the element is covered up when aligned to the top of the viewport, or if you prefer the element to be centered during scrolling of action commands. `scrollBehavior` accepts a single alignment, an object that sets each axis independently, or `false` to skip scrolling. Cypress translates the value into the options it hands to the browser's [`scrollIntoView()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) method. #### Aligning with a single value | Value | `scrollIntoView()` options | Result | | --- | --- | --- | | `'top'` | `{ block: 'start' }` | Aligns the element to the top of its scrollable container and leaves the horizontal position to the browser. This is the default. | | `'bottom'` | `{ block: 'end' }` | Aligns the element to the bottom of its scrollable container and leaves the horizontal position to the browser. | | `'start'` | `{ block: 'start', inline: 'start' }` | Aligns the element to the start of both axes, which is the top, leftmost point of its scrollable container in a left-to-right document. | | `'end'` | `{ block: 'end', inline: 'end' }` | Aligns the element to the end of both axes, which is the bottom, rightmost point of its scrollable container in a left-to-right document. | | `'center'` | `{ block: 'center', inline: 'center' }` | Centers the element within its scrollable container on both axes. | | `'nearest'` | `{ block: 'nearest', inline: 'nearest' }` | Scrolls the minimum amount needed on both axes to bring the element into view. If the element is above the visible area it aligns to the top; if it is below, it aligns to the bottom; if it is already fully visible, it does not scroll. | | `false` | Not applicable | Disables scrolling entirely before executing the command. | `'top'` and `'bottom'` describe the vertical axis only, the same way `scrollIntoView(true)` and `scrollIntoView(false)` do, so they never scroll a horizontally scrollable container that is already showing the element. Use `'start'` or `'end'` when you want both axes aligned. #### Aligning each axis independently Pass an object to control the two axes separately. `block` is the vertical axis and `inline` is the horizontal axis, and each takes the same values as the native `scrollIntoView()` method: `'start'`, `'end'`, `'center'`, or `'nearest'`. * cypress.config.js * cypress.config.ts ``` const { defineConfig } = require('cypress') module.exports = defineConfig({ scrollBehavior: { block: 'center', inline: 'nearest' }, }) ``` ``` import { defineConfig } from 'cypress' export default defineConfig({ scrollBehavior: { block: 'center', inline: 'nearest' }, }) ``` `'top'` and `'bottom'` are Cypress shorthands for a whole alignment rather than positions an axis can take, so they are not valid inside the object. Use `'start'` in place of `'top'` and `'end'` in place of `'bottom'`. An axis you leave out is omitted from the `scrollIntoView()` call, so the browser's own default applies: `'start'` for `block` and `'nearest'` for `inline`. #### Overriding the configured value A `scrollBehavior` passed to a command replaces the configured value rather than merging with it one axis at a time. With `scrollBehavior: { block: 'center' }` in your Cypress configuration, this click scrolls with `{ inline: 'start' }` alone, and `block` uses the browser default instead of staying `'center'`: ``` cy.get('button').click({ scrollBehavior: { inline: 'start' } }) ``` [Test configuration](/llm/markdown/app/references/configuration.md#Test-Configuration) overrides replace the global value the same way, so include every axis you care about in the value you pass. ### Coordinates After we verify the element is actionable, Cypress will then fire all of the appropriate events and corresponding default actions. Usually these events' coordinates are fired at the center of the element, but most commands enable you to change the position it's fired to. ``` cy.get('button').click({ position: 'topLeft' }) ``` The coordinates we fired the event at will generally be available when clicking the command in the [Command Log](/llm/markdown/app/core-concepts/open-mode.md#Command-Log). Additionally we'll display a red "hitbox" - which is a dot indicating the coordinates of the event. ## Debugging It can be difficult to debug problems when elements are not considered actionable by Cypress. Although you _should_ see a nice error message, nothing beats visually inspecting and poking at the DOM yourself to understand the reason why. When you use the [Command Log](/llm/markdown/app/core-concepts/open-mode.md#Command-Log) to hover over a command, you'll notice that we will always scroll the element the command was applied to into view. Please note that this is _NOT_ using the same algorithms that we described above. In fact we only ever scroll elements into view when actionable commands are running using the above algorithms. We _do not_ scroll elements into view on regular DOM queries like [`cy.get()`](/llm/markdown/api/commands/get.md) or [`.find()`](/llm/markdown/api/commands/find.md). The reason we scroll an element into view when hovering over a snapshot is to help you to see which element(s) were found by that corresponding command. It's a purely visual feature and does not necessarily reflect what your page looked like when the command ran. In other words, you cannot get a correct visual representation of what Cypress "saw" when looking at a previous snapshot. The only way for you to "see" and debug why Cypress thought an element was not visible is to use a `debugger` statement. We recommend placing `debugger` or using the [`.debug()`](/llm/markdown/api/commands/debug.md) command directly BEFORE the action. Make sure your Developer Tools are open and you can get pretty close to "seeing" the calculations Cypress is performing. You can also [bind to Events](/llm/markdown/api/cypress-api/catalog-of-events.md) that Cypress fires as it's working with your element. Using a debugger with these events will give you a much lower level view into how Cypress works. ``` // break on a debugger before the action command cy.get('button').debug().click() ``` ## Forcing While the above checks are super helpful at finding situations that would prevent your users from interacting with elements - sometimes they can get in the way! Sometimes it's not worth trying to "act like a user" to get a robot to do the exact steps a user would to interact with an element. Imagine you have a nested navigation structure where the user must hover over and move the mouse in a very specific pattern to reach the desired link. Is this worth trying to replicate when you're testing? Maybe not! For these scenarios, we give you an escape hatch to bypass all of the checks above and force events to happen! You can pass `{ force: true }` to most action commands. ``` // force the click and all subsequent events // to fire even if this element isn't considered 'actionable' cy.get('button').click({ force: true }) ``` **What's the difference?** When you force an event to happen we will: * Continue to perform all default actions * Forcibly fire the event at the element We will NOT perform these: * Scroll the element into view * Ensure it is visible * Ensure it is not disabled * Ensure it is not detached * Ensure it is not readonly * Ensure it is not animating * Ensure it is not covered * Fire the event at a descendent In summary, `{ force: true }` skips the checks, and it will always fire the event at the desired element. **force `.select()` disabled options** Passing `{ force: true }` to [.select()](/llm/markdown/api/commands/select.md) will not override the actionability checks for selecting a disabled ``. See [this issue](https://github.com/cypress-io/cypress/issues/107) for more detail. --- Source: https://docs.cypress.io/app/core-concepts/introduction-to-cypress.md Section: Cypress App # How Cypress works **This is the single most important guide** for understanding how to test with Cypress. Read it. Understand it. Ask questions about it so that we can improve it. ## Cypress Can Be Simple (Sometimes) Simplicity is all about getting more done with less typing. Let's look at an example: * End-to-End Test * Component Test ``` describe('Post Resource', () => { it('Creating a New Post', () => { cy.visit('/posts/new') // 1. cy.get("input.post-title") // 2. .type("My First Post"); // 3. cy.get("input.post-body") // 4. .type("Hello, world!"); // 5. cy.contains("Submit") // 6. .click(); // 7. cy.get("h1") // 8. .should("contain", "My First Post"); }); }); ``` ``` describe('Post Resource', () => { it('Creating a New Post', () => { cy.mount() // 1. cy.get("input.post-title") // 2. .type("My First Post"); // 3. cy.get("input.post-body") // 4. .type("Hello, world!"); // 5. cy.contains("Submit") // 6. .click(); // 7. cy.get("h1") // 8. .should("contain", "My First Post"); }); }); ``` Can you read this? If you did, it might sound something like this: 1. _Visit page at `/posts/new` (or mount the `PostBuilder` component)._ 2. _Find the `` with class `post-title`._ 3. _Type "My First Post" into it._ 4. _Find the `` with class `post-body`._ 5. _Type "Hello, world!" into it._ 6. _Find the element containing the text `Submit`._ 7. _Click it._ 8. _Find the `h1` tag, ensure it contains the text "My First Post"._ This is a relatively straightforward test, but consider how much code has been covered by it, both on the client and the server! For the remainder of this guide, we'll explore the basics of Cypress that make this example work. We'll demystify the rules Cypress follows so you can productively test your application to act as much like a user as possible, as well as discuss how to take shortcuts when it's useful. ## Querying Elements ### Cypress is Like jQuery If you've used [jQuery](https://jquery.com/) before, you may be used to querying for elements like this: ``` $('.my-selector') ``` In Cypress, querying elements is the same: ``` cy.get('.my-selector') ``` In fact, Cypress [bundles jQuery](/llm/markdown/app/references/bundled-libraries.md#Utility-libraries) and exposes many of its DOM traversal methods to you so you can work with complex HTML structures with ease using APIs you're already familiar with. ``` // Each Cypress query is equivalent to its jQuery counterpart. cy.get('#main-content').find('.article').children('img[src^="/static"]').first() ``` **Core Concept** Cypress leverages jQuery's powerful selector engine to help make tests familiar and readable for modern web developers. Interested in the best practices for selecting elements? [Read here](/llm/markdown/app/core-concepts/best-practices.md#Selecting-Elements). Accessing the DOM elements returned from the query works differently, however: ``` // This is fine, jQuery returns the element synchronously. const $jqElement = $('.element') // This will not work! Cypress does not return the element synchronously. const $cyElement = cy.get('.element') ``` Let's look at why this is... ### Cypress is _Not_ Like jQuery **Question:** What happens when jQuery can't find any matching DOM elements from its selector? **Answer:** _Oops!_ It returns an empty jQuery collection. We've got a real object to work with, but it doesn't contain the element we wanted. So we start adding conditional checks and retrying our queries manually. ``` // $() returns immediately with an empty collection. const $myElement = $('.element').first() // Leads to ugly conditional checks // and worse - flaky tests! if ($myElement.length) { doSomething($myElement) } ``` **Question:** What happens when Cypress can't find any matching DOM elements from its selector? **Answer:** _No big deal!_ Cypress automatically retries the query until either: #### 1\. The element is found ``` cy // cy.get() looks for '#element', repeating the query until... .get('#element') // ...it finds the element! // You can now work with it by using .then .then(($myElement) => { doSomething($myElement) }) ``` #### 2\. A set timeout is reached ``` cy // cy.get() looks for '#element-does-not-exist', repeating the query until... // ...it doesn't find the element before its timeout. // Cypress halts and fails the test. .get('#element-does-not-exist') // ...this code is never run... .then(($myElement) => { doSomething($myElement) }) ``` This makes Cypress robust and immune to dozens of common problems that occur in other testing tools. Consider all the circumstances that could cause querying a DOM element to fail: * The DOM has not loaded yet. * Your framework hasn't finished bootstrapping. * An XHR request hasn't responded. * An animation hasn't completed. * and on and on... Before, you'd be forced to write custom code to protect against any and all of these issues: a nasty mashup of arbitrary waits, conditional retries, and null checks littering your tests. Not in Cypress! With built-in retrying and [customizable timeouts](/llm/markdown/app/references/configuration.md#Timeouts), Cypress sidesteps all of these flaky issues. **Core Concept** Cypress wraps all DOM queries with robust retry-and-timeout logic that better suits how real web apps work. We trade a minor change in how we find DOM elements for a major stability upgrade to all of our tests. Banishing flake for good! In Cypress, when you want to interact with a DOM element directly, call [`.then()`](/llm/markdown/api/commands/then.md) with a callback function that receives the element as its first argument. When you want to skip the retry-and-timeout functionality entirely and perform traditional synchronous work, use [`Cypress.$`](/llm/markdown/api/utilities/$.md). ### Querying by Text Content Another way to locate things -- a more human way -- is to look them up by their content, by what the user would see on the page. For this, there's the handy [`cy.contains()`](/llm/markdown/api/commands/contains.md) command, for example: ``` // Find an element in the document containing the text 'New Post' cy.contains('New Post') // Find an element within '.main' containing the text 'New Post' cy.get('.main').contains('New Post') ``` This is helpful when writing tests from the perspective of a user interacting with your app. They only know that they want to click the button labeled "Submit". They have no idea that it has a `type` attribute of `submit`, or a CSS class of `my-submit-button`. **Internationalization** If your app is translated into multiple languages for i18n, make sure you consider the implications of using user-facing text to find DOM elements! ### When Elements Are Missing As we showed above, Cypress anticipates the asynchronous nature of web applications and doesn't fail immediately the first time an element is not found. Instead, Cypress gives your app a window of time to finish whatever it may be doing! This is known as a `timeout`, and most commands can be customized with specific timeout periods ([the default timeout is 4 seconds](/llm/markdown/app/references/configuration.md#Timeouts)). These Commands will list a `timeout` option in their API documentation, detailing how to set the number of milliseconds you want to continue to try finding the element. ``` // Give this element 10 seconds to appear cy.get('.my-slow-selector', { timeout: 10000 }) ``` You can also set the timeout globally via the [configuration setting: `defaultCommandTimeout`](/llm/markdown/app/references/configuration.md#Timeouts). **Core Concept** To match the behavior of web applications, Cypress is asynchronous and relies on timeouts to know when to stop waiting on an app to get into the expected state. Timeouts can be configured globally, or on a per-command basis. **Timeouts and Performance** There is a performance tradeoff here: **tests that have longer timeout periods take longer to fail**. Commands always proceed as soon as their expected criteria is met, so working tests will be performed as fast as your application allows. A test that fails due to timeout will consume the entire timeout period, by design. This means that while you _may_ want to increase your timeout period to suit specific parts of your app, you _don't_ want to make it "extra long, just in case". Later in this guide we'll go into much more detail about [Implicit Assertions](#Implicit-Assertions) and [Timeouts](#Timeouts). ## Chains of Commands It's very important to understand the mechanism Cypress uses to chain commands together. It manages a Promise chain on your behalf, with each command yielding a 'subject' to the next command, until the chain ends or an error is encountered. The developer should not need to use Promises directly, but understanding how they work is helpful! ### Interacting With Elements As we saw in the initial example, Cypress allows you to click on and type into elements on the page by using [`.click()`](/llm/markdown/api/commands/click.md) and [`.type()`](/llm/markdown/api/commands/type.md) action commands with a [`cy.get()`](/llm/markdown/api/commands/get.md) or [`cy.contains()`](/llm/markdown/api/commands/contains.md) query command. This is a great example of chaining in action. Let's see it again: ``` cy.get('textarea.post-body').type('This is an excellent post.') ``` We're chaining [`.type()`](/llm/markdown/api/commands/type.md) onto [`cy.get()`](/llm/markdown/api/commands/get.md), telling it to type into the subject yielded from the [`cy.get()`](/llm/markdown/api/commands/get.md) query, which will be a DOM element. Here are even more action commands Cypress provides to interact with your app: * [`.blur()`](/llm/markdown/api/commands/blur.md) - Make a focused DOM element blur. * [`.focus()`](/llm/markdown/api/commands/focus.md) - Focus on a DOM element. * [`.clear()`](/llm/markdown/api/commands/clear.md) - Clear the value of an input or textarea. * [`.check()`](/llm/markdown/api/commands/check.md) - Check checkbox(es) or radio(s). * [`.uncheck()`](/llm/markdown/api/commands/uncheck.md) - Uncheck checkbox(es). * [`.select()`](/llm/markdown/api/commands/select.md) - Select an `