Cypress v14 Component Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Cypress v14 Component Testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert in Cypress v14 component testing. When the user asks you to set up component testing with Cypress, write component tests for React/Vue/Angular components, configure mount commands, or integrate with Vite or Webpack, follow these detailed instructions.
src/
components/
Button/
Button.tsx
Button.cy.tsx
Button.module.css
Form/
LoginForm.tsx
LoginForm.cy.tsx
Modal/
Modal.tsx
Modal.cy.tsx
DataTable/
DataTable.tsx
DataTable.cy.tsx
cypress/
support/
component.ts
commands.ts
mount-utils.tsx
fixtures/
test-data.json
cypress.config.ts// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.{ts,tsx}',
supportFile: 'cypress/support/component.ts',
indexHtmlFile: 'cypress/support/component-index.html',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true,
},
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{ts,tsx}',
},
});// cypress/support/mount-utils.tsx
import React from 'react';
import { mount } from 'cypress/react';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from '../src/providers/theme-provider';
interface MountOptions {
routerProps?: Record<string, any>;
queryClient?: QueryClient;
theme?: 'light' | 'dark';
initialRoute?: string;
}
export function mountWithProviders(
component: React.ReactElement,
options: MountOptions = {}
) {
const queryClient = options.queryClient || new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
},
});
const wrapped = (
<QueryClientProvider client={queryClient}>
<ThemeProvider defaultTheme={options.theme || 'light'}>
<BrowserRouter>
{component}
</BrowserRouter>
</ThemeProvider>
</QueryClientProvider>
);
return mount(wrapped);
}
// Register as custom command
declare global {
namespace Cypress {
interface Chainable {
mountWithProviders(
component: React.ReactElement,
options?: MountOptions
): Cypress.Chainable<any>;
}
}
}
Cypress.Commands.add('mountWithProviders', mountWithProviders);// cypress/support/component.ts
import './commands';
import './mount-utils';
// Import global styles
import '../../src/index.css';
// Import cypress-axe for accessibility testing
import 'cypress-axe';
// Prevent uncaught exceptions from failing tests
Cypress.on('uncaught:exception', (err) => {
if (err.message.includes('ResizeObserver loop')) return false;
return true;
});// src/components/Button/Button.cy.tsx
import { Button } from './Button';
describe('Button Component', () => {
it('renders with default props', () => {
cy.mount(<Button>Click me</Button>);
cy.get('button').should('be.visible').and('contain.text', 'Click me');
});
it('renders different variants', () => {
const variants = ['primary', 'secondary', 'outline', 'ghost', 'destructive'] as const;
variants.forEach((variant) => {
cy.mount(<Button variant={variant}>Button</Button>);
cy.get('button').should('have.attr', 'data-variant', variant);
});
});
it('renders different sizes', () => {
cy.mount(<Button size="sm">Small</Button>);
cy.get('button').should('have.class', 'btn-sm');
cy.mount(<Button size="lg">Large</Button>);
cy.get('button').should('have.class', 'btn-lg');
});
it('handles click events', () => {
const onClick = cy.stub().as('clickHandler');
cy.mount(<Button onClick={onClick}>Click me</Button>);
cy.get('button').click();
cy.get('@clickHandler').should('have.been.calledOnce');
});
it('is disabled when disabled prop is true', () => {
const onClick = cy.stub().as('clickHandler');
cy.mount(<Button disabled onClick={onClick}>Disabled</Button>);
cy.get('button').should('be.disabled');
cy.get('button').click({ force: true });
cy.get('@clickHandler').should('not.have.been.called');
});
it('shows loading state', () => {
cy.mount(<Button loading>Loading</Button>);
cy.get('button').should('be.disabled');
cy.get('[data-testid="spinner"]').should('be.visible');
});
it('renders as a link when asChild is provided', () => {
cy.mount(
<Button asChild>
<a href="/home">Home</a>
</Button>
);
cy.get('a').should('have.attr', 'href', '/home');
});
it('passes accessibility audit', () => {
cy.mount(<Button>Accessible Button</Button>);
cy.injectAxe();
cy.checkA11y('button');
});
});// src/components/Form/LoginForm.cy.tsx
import { LoginForm } from './LoginForm';
describe('LoginForm Component', () => {
const defaultProps = {
onSubmit: cy.stub().as('submitHandler'),
onForgotPassword: cy.stub().as('forgotHandler'),
};
beforeEach(() => {
cy.mountWithProviders(<LoginForm {...defaultProps} />);
});
it('renders all form fields', () => {
cy.get('input[name="email"]').should('be.visible');
cy.get('input[name="password"]').should('be.visible');
cy.get('button[type="submit"]').should('be.visible').and('contain.text', 'Sign In');
});
it('submits form with valid data', () => {
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('Password123!');
cy.get('button[type="submit"]').click();
cy.get('@submitHandler').should('have.been.calledOnceWith', {
email: '[email protected]',
password: 'Password123!',
});
});
it('shows validation errors for empty submission', () => {
cy.get('button[type="submit"]').click();
cy.contains('Email is required').should('be.visible');
cy.contains('Password is required').should('be.visible');
cy.get('@submitHandler').should('not.have.been.called');
});
it('validates email format', () => {
cy.get('input[name="email"]').type('not-an-email');
cy.get('input[name="email"]').blur();
cy.contains('Please enter a valid email').should('be.visible');
});
it('validates password minimum length', () => {
cy.get('input[name="password"]').type('short');
cy.get('input[name="password"]').blur();
cy.contains('Password must be at least 8 characters').should('be.visible');
});
it('toggles password visibility', () => {
cy.get('input[name="password"]').type('MyPassword');
cy.get('input[name="password"]').should('have.attr', 'type', 'password');
cy.get('[data-testid="toggle-password"]').click();
cy.get('input[name="password"]').should('have.attr', 'type', 'text');
cy.get('[data-testid="toggle-password"]').click();
cy.get('input[name="password"]').should('have.attr', 'type', 'password');
});
it('handles forgot password link', () => {
cy.contains('Forgot password?').click();
cy.get('@forgotHandler').should('have.been.calledOnce');
});
it('shows loading state during submission', () => {
const slowSubmit = cy.stub().callsFake(() => new Promise((r) => setTimeout(r, 1000)));
cy.mountWithProviders(<LoginForm onSubmit={slowSubmit} />);
cy.get('input[name="email"]').type('[email protected]');
cy.get('input[name="password"]').type('Password123!');
cy.get('button[type="submit"]').click();
cy.get('button[type="submit"]').should('be.disabled');
cy.get('[data-testid="spinner"]').should('be.visible');
});
it('passes accessibility audit', () => {
cy.injectAxe();
cy.checkA11y('form');
});
});// src/components/Modal/Modal.cy.tsx
import { Modal } from './Modal';
import { useState } from 'react';
function ModalWrapper(props: Partial<React.ComponentProps<typeof Modal>>) {
const [open, setOpen] = useState(true);
return (
<Modal open={open} onClose={() => setOpen(false)} {...props}>
<Modal.Title>Test Modal</Modal.Title>
<Modal.Content>Modal content goes here</Modal.Content>
<Modal.Footer>
<button onClick={() => setOpen(false)}>Close</button>
<button>Confirm</button>
</Modal.Footer>
</Modal>
);
}
describe('Modal Component', () => {
it('renders when open is true', () => {
cy.mount(<ModalWrapper />);
cy.get('[role="dialog"]').should('be.visible');
cy.contains('Test Modal').should('be.visible');
cy.contains('Modal content goes here').should('be.visible');
});
it('closes when close button is clicked', () => {
cy.mount(<ModalWrapper />);
cy.contains('Close').click();
cy.get('[role="dialog"]').should('not.exist');
});
it('closes on Escape key press', () => {
cy.mount(<ModalWrapper />);
cy.get('body').type('{esc}');
cy.get('[role="dialog"]').should('not.exist');
});
it('closes on backdrop click', () => {
cy.mount(<ModalWrapper />);
cy.get('[data-testid="modal-backdrop"]').click({ force: true });
cy.get('[role="dialog"]').should('not.exist');
});
it('traps focus within the modal', () => {
cy.mount(<ModalWrapper />);
cy.get('[role="dialog"]').should('be.visible');
// Tab through focusable elements
cy.get('body').tab();
cy.focused().should('contain.text', 'Close');
cy.get('body').tab();
cy.focused().should('contain.text', 'Confirm');
});
it('renders with custom size', () => {
cy.mount(<ModalWrapper size="lg" />);
cy.get('[role="dialog"]').should('have.class', 'modal-lg');
});
it('passes accessibility audit', () => {
cy.mount(<ModalWrapper />);
cy.injectAxe();
cy.checkA11y('[role="dialog"]');
});
});// src/components/DataTable/DataTable.cy.tsx
import { DataTable } from './DataTable';
const mockData = [
{ id: 1, name: 'Alice', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Bob', email: '[email protected]', role: 'User' },
{ id: 3, name: 'Charlie', email: '[email protected]', role: 'Editor' },
];
const columns = [
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true },
{ key: 'role', header: 'Role', sortable: false },
];
describe('DataTable Component', () => {
it('renders all rows and columns', () => {
cy.mount(<DataTable data={mockData} columns={columns} />);
// Check headers
cy.get('th').should('have.length', 3);
cy.get('th').eq(0).should('contain.text', 'Name');
// Check rows
cy.get('tbody tr').should('have.length', 3);
cy.get('tbody tr').first().should('contain.text', 'Alice');
});
it('sorts by column when header is clicked', () => {
cy.mount(<DataTable data={mockData} columns={columns} />);
// Sort by name ascending
cy.contains('th', 'Name').click();
cy.get('tbody tr').first().should('contain.text', 'Alice');
// Sort by name descending
cy.contains('th', 'Name').click();
cy.get('tbody tr').first().should('contain.text', 'Charlie');
});
it('renders empty state when no data', () => {
cy.mount(<DataTable data={[]} columns={columns} />);
cy.contains('No data available').should('be.visible');
});
it('handles row selection', () => {
const onSelect = cy.stub().as('selectHandler');
cy.mount(<DataTable data={mockData} columns={columns} selectable onSelectionChange={onSelect} />);
cy.get('tbody tr').first().find('input[type="checkbox"]').check();
cy.get('@selectHandler').should('have.been.calledWith', [mockData[0]]);
});
it('supports pagination', () => {
const manyRows = Array.from({ length: 25 }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@test.com`,
role: 'User',
}));
cy.mount(<DataTable data={manyRows} columns={columns} pageSize={10} />);
cy.get('tbody tr').should('have.length', 10);
cy.contains('button', 'Next').click();
cy.get('tbody tr').should('have.length', 10);
cy.contains('button', 'Next').click();
cy.get('tbody tr').should('have.length', 5);
});
});// src/components/Navigation/Navigation.cy.tsx
import { Navigation } from './Navigation';
describe('Navigation Responsive Behavior', () => {
it('shows full menu on desktop', () => {
cy.viewport(1280, 720);
cy.mountWithProviders(<Navigation />);
cy.get('nav').should('be.visible');
cy.get('[data-testid="desktop-menu"]').should('be.visible');
cy.get('[data-testid="mobile-menu-button"]').should('not.be.visible');
});
it('shows hamburger menu on mobile', () => {
cy.viewport(375, 812);
cy.mountWithProviders(<Navigation />);
cy.get('[data-testid="mobile-menu-button"]').should('be.visible');
cy.get('[data-testid="desktop-menu"]').should('not.be.visible');
});
it('toggles mobile menu on hamburger click', () => {
cy.viewport(375, 812);
cy.mountWithProviders(<Navigation />);
cy.get('[data-testid="mobile-menu-button"]').click();
cy.get('[data-testid="mobile-menu"]').should('be.visible');
cy.get('[data-testid="mobile-menu-button"]').click();
cy.get('[data-testid="mobile-menu"]').should('not.be.visible');
});
it('closes mobile menu on navigation', () => {
cy.viewport(375, 812);
cy.mountWithProviders(<Navigation />);
cy.get('[data-testid="mobile-menu-button"]').click();
cy.get('[data-testid="mobile-menu"]').should('be.visible');
cy.get('[data-testid="mobile-menu"]').contains('Home').click();
cy.get('[data-testid="mobile-menu"]').should('not.be.visible');
});
});.cy.tsx files next to their component files for easy discovery and maintenance.cy.injectAxe() and cy.checkA11y() to component tests for automated accessibility validation.cy.viewport() to test components at different screen sizes within component tests.div > div > button.primary. Use roles, labels, and test IDs.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.