How to Test Vue.js Apps with Cypress
Set up Cypress Component Testing for Vue, write a first browser-based test, add the app plugins your components need, and choose when to use E2E tests.
To test a Vue.js app with Cypress, install Cypress, configure Component Testing for Vue and your bundler, then mount a component in a real browser and assert on what a user can see or do. Use component tests for focused UI behavior; use end-to-end (E2E) tests when you need to verify a whole application flow, including startup and routing.
This guide uses Vue 3 with Vite. Cypress’s Vue Component Testing documentation currently lists Vue 3+, Vite 8.x, and Webpack 5+ as supported. These compatibility details can change, so check the current Cypress Vue overview before setup.
1. Choose the test scope
Cypress Component Testing mounts an individual Vue component in a real browser within Cypress’s test environment. E2E testing exercises an application running as a whole in a browser. Choose based on the claim a test should verify:
| Test type | What it covers | Use it to check |
|---|---|---|
| Component | A component and the app setup it needs | Whether props, rendering, and user actions produce the expected UI or event |
| E2E | The running application and an integrated user workflow | Whether a user can enter the app, navigate, and complete a flow across features |
These scopes complement each other. A component test is usually the direct choice for a focused control; an E2E test is appropriate when the route, application startup, or integration between parts is part of the requirement. Cypress describes component tests as rendering components directly in a real browser rather than a simulated DOM (Cypress Component Testing introduction).
2. Install Cypress and configure Vue Component Testing
Install Cypress as a development dependency, then open its Launchpad to configure a component testing project:
npm install --save-dev cypress
npx cypress open
In Launchpad, choose Component Testing, then select Vue and the bundler used by the project. For a Vite project, the relevant configuration has this shape in cypress.config.js:
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'vue',
bundler: 'vite',
},
},
})
Use the configuration generated for your project if it differs, especially if it is an ES module project or has existing Cypress settings. For Webpack, choose webpack and make sure the project’s Webpack configuration can compile its Vue components.
The component dev server compiles specs and support files with the app’s development transforms and serves the compiled files to Cypress. As a result, bundler aliases, CSS transforms, and other build settings can affect whether a component mounts. See Cypress component framework configuration when the default dev server setup does not match the app.
3. Write and run a first component test
Create a component such as src/components/Stepper.vue:
<script setup>
import { ref } from 'vue'
defineProps({ initial: { type: Number, default: 0 } })
const count = ref(0)
</script>
<template>
<section>
<output data-cy="count">{{ count + initial }}</output>
<button type="button" @click="count++">Increment</button>
</section>
</template>
Register Cypress’s Vue mount function as the cy.mount() command in cypress/support/component.js:
import { mount } from 'cypress/vue'
Cypress.Commands.add('mount', mount)
Make sure the component support file is included by the component configuration. Cypress Launchpad sets this up for a new project; an existing project should follow its generated support-file path.
Now add src/components/Stepper.cy.vue or a spec file such as cypress/component/Stepper.cy.js, according to the project’s Cypress spec pattern:
import Stepper from '../../src/components/Stepper.vue'
describe('Stepper', () => {
it('increments the displayed value when clicked', () => {
cy.mount(Stepper, { props: { initial: 3 } })
cy.get('[data-cy="count"]').should('have.text', '3')
cy.contains('button', 'Increment').click()
cy.get('[data-cy="count"]').should('have.text', '4')
})
})
Run the component spec in the Cypress app. The test mounts the component, checks its initial output, performs a user action, and checks the resulting output. Prefer stable selectors such as data-cy when text or styling may change independently of behavior.
Cypress’s documented Vue example also shows passing props and attaching a spy to an event prop, then asserting that clicking a control emits the updated value. This tests an externally observable result rather than component internals. See Cypress Vue examples.
4. Pass props and assert emitted events
For a component that emits an event, a Cypress spy can verify its value. For example, a Vue component might declare an onChange prop and call it when a control changes:
import Stepper from '../../src/components/Stepper.vue'
describe('Stepper events', () => {
it('reports the updated value', () => {
const onChange = cy.spy().as('onChange')
cy.mount(Stepper, {
props: {
initial: 5,
onChange,
},
})
cy.contains('button', 'Increment').click()
cy.get('@onChange').should('have.been.calledWith', 6)
})
})
The component must actually call the supplied handler for this example to pass. If it uses Vue’s defineEmits instead, use the event-listening pattern supported by the installed Cypress Vue integration and assert on the event payload. The test should match the component’s public contract.
5. Recreate the app setup the component requires
A component mounted alone does not automatically inherit the application’s plugins, global components, router, or provider hierarchy. If many tests need the same setup, make a custom mount command that creates the required app context. Create fresh stores per test so state does not leak from one test to another.
Pinia and a shared mount command
import { mount } from 'cypress/vue'
import { createPinia } from 'pinia'
Cypress.Commands.add('mount', (component, options = {}) => {
const pinia = createPinia()
return mount(component, {
...options,
global: {
...options.global,
plugins: [...(options.global?.plugins ?? []), pinia],
},
})
})
Adapt this pattern to the project’s plugin initialization. Cypress’s Vue examples cover Pinia, Vue I18n, Vue Router with memory history, Vuex, global components, and Vuetify’s required VApp hierarchy. See the official examples for those integrations.
Router-dependent components
For a component that uses router links or route state, register a router in the mount setup and use memory history where appropriate. Start each test at the route it needs, rather than relying on navigation left behind by an earlier test. If the behavior under test includes actual application startup and navigation between pages, consider an E2E test instead.
Nuxt projects
Cypress’s Vue component setup does not consume nuxt.config and there is no dedicated Nuxt framework definition in the cited Vue overview. Nuxt aliases such as ~ and @, auto-imports, and Nuxt plugins are therefore not automatically available when mounting a component by itself. Configure the aliases and transforms in the component bundler, import dependencies explicitly, or add the required plugin setup to the test mount. Check the Cypress Vue overview for current Nuxt guidance.
6. Component testing versus E2E for Vue
| Question | Component test | E2E test |
|---|---|---|
| What runs? | A mounted component with its required setup | The application as a whole in a browser |
| What question does it answer? | Does this UI respond correctly to props and user actions? | Can the user complete an integrated flow from an app entry point? |
| What setup matters? | Component dev server, bundler transforms, plugins, and wrappers | Running app and the environment needed for the workflow |
The distinction in scope follows Cypress’s descriptions of component and E2E testing; the question each test answers is practical guidance based on that distinction (Why Cypress? E2E and component testing). This article focuses on component testing setup. For E2E setup, choose Cypress’s E2E configuration in Launchpad and follow the current E2E guide for the project’s framework and server.
7. Troubleshoot common problems
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Cypress cannot find or mount the Vue component | The component dev server is missing the Vue framework or bundler configuration, or the spec imports the wrong path | Confirm the component config uses framework: 'vue', the correct bundler, and a valid relative import. Reopen Launchpad to check the generated support and spec paths. |
| Vite or Webpack reports an unsupported version | The project’s bundler version falls outside current Cypress Vue support | Compare the installed version with the current Vue compatibility page; the researched documentation listed Vite 8.x and Webpack 5+. |
An import using @ or ~ fails |
The alias exists in the app config but is not configured for the component dev server | Mirror the alias in the component bundler config or use an explicit relative import in the spec. |
| A component complains that a plugin, store, or injection is missing | Mounting does not run the full application bootstrap | Register the needed plugin or provider in a shared mount command, or mount the component inside its required wrapper. |
| Tests pass alone but fail in a suite | Shared mutable state, such as a store, may be reused across tests | Create the store and other mutable app state inside the mount command for each test. |
| Styles or CSS imports fail during mounting | The component dev server is not applying the app’s expected transforms or style setup | Check the bundler configuration, CSS preprocessing, and support-file imports used by the component test environment. |
| The event spy is never called | The component does not invoke the passed handler, the test listens to the wrong event interface, or the click does not reach the control | Check the component’s declared public API and event payload, then assert against the handler or Vue event mechanism it actually uses. |
| Nuxt auto-imports are undefined | Cypress does not read nuxt.config for component mounting |
Add the needed imports, plugins, aliases, or transforms explicitly to the test setup. |
8. Keep tests reliable and costs predictable
- Assert on visible outcomes. Check rendered text, accessible controls, or documented events rather than private component state.
- Keep component setup narrow. Add only the plugins and wrappers needed by the behavior under test. This makes a failure easier to localize.
- Isolate mutable state. Create a fresh store and app instance for each mount when state can change.
- Use browser-realistic behavior intentionally. Component Testing runs in a real browser, so bundler configuration and browser behavior are part of the environment.
- Choose scope deliberately. A component test can focus on UI behavior; an E2E test covers integrated flows. Neither scope proves every behavior of the other.
- Plan execution cost around the project. Cypress is installed as a development dependency; execution time and any CI or browser infrastructure cost depend on how and where the project runs. The cited documentation does not provide a universal runtime or cost benchmark.
Or skip the browser setup
If you need a screenshot of a Vue app or a rendered route for review or documentation, ScreenshotNeo provides a website screenshot API and MCP server. This does not replace Cypress assertions: Cypress checks application behavior, while ScreenshotNeo returns a screenshot or PDF.
One GET request returns an image or PDF. For a clean WebP screenshot of a public page, use the API key from your account. See the ScreenshotNeo API documentation for parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
- Cookie banners are accepted and removed before the shot; more than 60 known consent platforms, newsletter popups, and chat widgets can be removed, and each step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
FAQ
Does Cypress Component Testing run Vue components in a real browser?
Yes. Cypress mounts components in a browser as part of its component testing environment.
Can I use Cypress with Vue 2?
The current Vue Component Testing overview cited here describes Vue 3+. Check Cypress’s current compatibility documentation before planning a Vue 2 setup.
Do component tests need the whole Vue app to run?
No. They mount a component with the setup it needs. Use E2E testing when the full app workflow is what you need to verify.
Does Cypress automatically use Nuxt aliases and auto-imports?
No. The Vue component setup does not consume nuxt.config, so configure the relevant imports, aliases, and plugins for the component test environment.
Can ScreenshotNeo replace Cypress tests?
No. ScreenshotNeo captures pages as images or PDFs; Cypress runs tests and assertions about behavior.


