Mount and test Vue components with Vue Test Utils integration and Cypress commands. The @cypress/vue package provides seamless integration between Vue components and Cypress's powerful testing capabilities.
Mount Vue components for isolated testing with props, slots, and full Cypress command support.
// From cypress/vue
import { mount } from '@cypress/vue';
/**
* Mount a Vue component for testing
* @param component - Vue component to mount
* @param options - Vue Test Utils mounting options plus Cypress-specific options
* @returns Cypress chainable with wrapper and component access
*/
function mount<T>(
component: T,
options?: VueMountOptions<T>
): Cypress.Chainable<VueMountReturn<T>>;
interface VueMountOptions<T> {
/** Props to pass to the component */
props?: Record<string, any>;
/** Slots content */
slots?: Record<string, any>;
/** Global configuration */
global?: {
plugins?: any[];
components?: Record<string, any>;
directives?: Record<string, any>;
mixins?: any[];
provide?: Record<string, any>;
config?: {
globalProperties?: Record<string, any>;
errorHandler?: (err: Error) => void;
};
};
/** Whether to log the mount command */
log?: boolean;
}
interface VueMountReturn<T> {
/** Vue Test Utils wrapper for advanced interactions */
wrapper: VueWrapper<ComponentPublicInstance>;
/** Direct access to the component instance */
component: ComponentPublicInstance;
}Usage Examples:
// cypress/component/Button.cy.js
import { mount } from '@cypress/vue';
import Button from './Button.vue';
describe('Button Component', () => {
it('renders with text', () => {
mount(Button, {
slots: {
default: 'Click me'
}
});
cy.contains('Click me').should('be.visible');
});
it('emits click events', () => {
const onClickSpy = cy.stub();
mount(Button, {
props: {
onClick: onClickSpy
},
slots: {
default: 'Click me'
}
});
cy.contains('Click me').click();
cy.wrap(onClickSpy).should('have.been.called');
});
it('supports different variants', () => {
mount(Button, {
props: {
variant: 'primary'
},
slots: {
default: 'Primary Button'
}
});
cy.get('button').should('have.class', 'btn-primary');
});
});Test components with props and watch for reactive updates.
// Testing reactive props
describe('Counter Component', () => {
it('displays initial count', () => {
mount(Counter, {
props: {
initialCount: 5
}
});
cy.get('[data-cy=count]').should('contain', '5');
});
it('updates when props change', () => {
mount(Counter, {
props: {
initialCount: 0
}
}).then(({ wrapper }) => {
cy.get('[data-cy=count]').should('contain', '0');
// Update props programmatically
wrapper.setProps({ initialCount: 10 });
cy.get('[data-cy=count]').should('contain', '10');
});
});
it('handles user interactions', () => {
mount(Counter, {
props: {
initialCount: 0
}
});
cy.get('[data-cy=increment]').click();
cy.get('[data-cy=count]').should('contain', '1');
cy.get('[data-cy=decrement]').click();
cy.get('[data-cy=count]').should('contain', '0');
});
});Test components that use Vue slots for content projection.
import { mount } from '@cypress/vue';
import Card from './Card.vue';
describe('Card Component', () => {
it('renders default slot content', () => {
mount(Card, {
slots: {
default: '<p>Card content</p>'
}
});
cy.contains('Card content').should('be.visible');
});
it('renders named slots', () => {
mount(Card, {
slots: {
header: '<h2>Card Title</h2>',
default: '<p>Card body content</p>',
footer: '<button>Action</button>'
}
});
cy.contains('Card Title').should('be.visible');
cy.contains('Card body content').should('be.visible');
cy.contains('Action').should('be.visible');
});
it('renders scoped slots', () => {
mount(Card, {
slots: {
default: `
<template #default="{ user }">
<p>Hello, {{ user.name }}!</p>
</template>
`
},
props: {
user: { name: 'John Doe' }
}
});
cy.contains('Hello, John Doe!').should('be.visible');
});
});Test components that depend on Vue plugins like Vue Router or Vuex/Pinia.
import { mount } from '@cypress/vue';
import { createRouter, createWebHistory } from 'vue-router';
import Navigation from './Navigation.vue';
describe('Navigation Component', () => {
const routes = [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/about', component: { template: '<div>About</div>' } },
{ path: '/contact', component: { template: '<div>Contact</div>' } }
];
it('highlights active route', () => {
const router = createRouter({
history: createWebHistory(),
routes
});
mount(Navigation, {
global: {
plugins: [router]
}
});
cy.get('[data-cy=nav-home]').should('have.class', 'router-link-active');
});
it('navigates to different routes', () => {
const router = createRouter({
history: createWebHistory(),
routes
});
mount(Navigation, {
global: {
plugins: [router]
}
});
cy.get('[data-cy=nav-about]').click();
cy.location('pathname').should('eq', '/about');
});
});Test components that use Pinia for state management.
import { mount } from '@cypress/vue';
import { createPinia, setActivePinia } from 'pinia';
import { useUserStore } from './stores/user';
import UserProfile from './UserProfile.vue';
describe('UserProfile Component', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it('displays user information from store', () => {
const pinia = createPinia();
mount(UserProfile, {
global: {
plugins: [pinia]
}
}).then(() => {
// Access store and set user data
const userStore = useUserStore();
userStore.setUser({
name: 'Jane Doe',
email: 'jane@example.com'
});
cy.contains('Jane Doe').should('be.visible');
cy.contains('jane@example.com').should('be.visible');
});
});
it('updates when store state changes', () => {
const pinia = createPinia();
mount(UserProfile, {
global: {
plugins: [pinia]
}
}).then(() => {
const userStore = useUserStore();
// Initial state
userStore.setUser({ name: 'John', email: 'john@example.com' });
cy.contains('John').should('be.visible');
// Update state
userStore.updateName('Johnny');
cy.contains('Johnny').should('be.visible');
});
});
});Test components that depend on globally registered components or directives.
import { mount } from '@cypress/vue';
import MyComponent from './MyComponent.vue';
import GlobalButton from './GlobalButton.vue';
describe('MyComponent', () => {
it('uses global components', () => {
mount(MyComponent, {
global: {
components: {
'global-button': GlobalButton
}
}
});
cy.get('global-button').should('exist');
});
it('uses custom directives', () => {
const focusDirective = {
mounted(el) {
el.focus();
}
};
mount(MyComponent, {
global: {
directives: {
focus: focusDirective
}
}
});
cy.get('[data-cy=input-with-focus]').should('be.focused');
});
});Test components that use Vue 3's Composition API.
import { mount } from '@cypress/vue';
import CompositionComponent from './CompositionComponent.vue';
describe('CompositionComponent', () => {
it('handles reactive refs', () => {
mount(CompositionComponent);
cy.get('[data-cy=counter]').should('contain', '0');
cy.get('[data-cy=increment]').click();
cy.get('[data-cy=counter]').should('contain', '1');
});
it('handles computed properties', () => {
mount(CompositionComponent, {
props: {
items: ['apple', 'banana', 'cherry']
}
});
cy.get('[data-cy=item-count]').should('contain', '3');
cy.get('[data-cy=first-item]').should('contain', 'apple');
});
it('handles watchers', () => {
mount(CompositionComponent);
cy.get('[data-cy=search-input]').type('test');
cy.get('[data-cy=search-results]').should('contain', 'Searching for: test');
});
});Access Vue Test Utils functionality through the wrapper returned by mount.
// Re-exported Vue Test Utils (excluding mount and shallowMount)
import { VueTestUtils } from '@cypress/vue';
// Available Vue Test Utils methods:
interface VueWrapper {
/** Find component by selector */
find(selector: string): DOMWrapper;
/** Find component by component definition */
findComponent(component: any): VueWrapper;
/** Find all matching elements */
findAll(selector: string): DOMWrapper[];
/** Set component props */
setProps(props: Record<string, any>): Promise<void>;
/** Set component data */
setData(data: Record<string, any>): Promise<void>;
/** Trigger an event */
trigger(event: string, options?: any): Promise<void>;
/** Get component's emitted events */
emitted(): Record<string, any[]>;
/** Check if component exists */
exists(): boolean;
/** Get component HTML */
html(): string;
/** Get component text */
text(): string;
/** Unmount the component */
unmount(): void;
}Advanced Testing with Vue Test Utils:
describe('Advanced Component Testing', () => {
it('uses wrapper methods for detailed testing', () => {
mount(ComplexComponent, {
props: {
items: ['item1', 'item2', 'item3']
}
}).then(({ wrapper }) => {
// Use Vue Test Utils methods
expect(wrapper.findAll('[data-cy=item]')).to.have.length(3);
// Check emitted events
wrapper.find('[data-cy=delete-button]').trigger('click');
expect(wrapper.emitted()).to.have.property('delete');
// Update props and verify changes
wrapper.setProps({ items: ['item1', 'item2'] });
cy.get('[data-cy=item]').should('have.length', 2);
});
});
});Configure Vue component testing in your Cypress configuration:
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
component: {
devServer: {
framework: 'vue',
bundler: 'vite', // or 'webpack'
},
specPattern: 'src/**/*.cy.{js,ts}',
supportFile: 'cypress/support/component.js'
}
});Create custom commands for Vue-specific testing patterns:
// cypress/support/component.js
import { mount } from '@cypress/vue';
Cypress.Commands.add('mountVue', (component, options = {}) => {
// Add default global configuration
const defaultOptions = {
global: {
stubs: {
// Stub router-link by default
'router-link': true
}
}
};
const mergedOptions = { ...defaultOptions, ...options };
return mount(component, mergedOptions);
});
// Usage in tests
cy.mountVue(MyComponent, {
props: { title: 'Test Title' }
});The Vue component testing integration provides comprehensive support for testing Vue.js components in isolation while maintaining access to Vue's reactive system, component lifecycle, and ecosystem tools.