| name | cypress |
| description | Tests E2E et composants avec Cypress — installation, API, custom commands, CI, mocks réseau, component testing, et bonnes pratiques. |
| version | 1.0.0 |
| author | EVA |
| license | MIT |
| metadata | {"hermes":{"tags":["cypress","e2e","testing","javascript","web","component-testing","ci"],"related_skills":["playwright","selenium","jest","e2e-testing"]}} |
Cypress — Tests E2E et Component Testing
Overview
Cypress est un framework de test E2E nouvelle génération (pas basé sur Selenium). Architecture en boucle interne : le test et l'application tournent dans le même processus. Time Travel, débogage natif, captures d'écran/vidéo automatiques.
When to Use
- Tests E2E d'applications web (React, Vue, Angular, Next.js, Nuxt)
- Tests de composants d'UI modernes
- Tests d'intégration frontend avec API mockées
- Ne pas utiliser pour : crawling/scraping lourd, tests backend purs, tests multi-onglets ou multi-domaines (Cypress ne gère qu'un seul domaine visité)
Installation
npm install --save-dev cypress @cypress/react @cypress/vite-dev-server
npx cypress install
npx cypress open
npx cypress run
Structure générée :
cypress/
├── e2e/ # Tests E2E (.cy.js / .cy.ts)
├── component/ # Tests composants
├── fixtures/ # Données mock (JSON, images)
├── support/
│ ├── commands.ts # Commandes personnalisées
│ └── e2e.ts # Configuration globale E2E
└── downloads/ # Fichiers téléchargés pendant les tests
Configuration cypress.config.ts :
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
video: true,
screenshotOnRunFailure: true,
excludeXHRInNetworkLogs: true,
setupNodeEvents(on, config) {
on('task', {
log(message) { console.log(message); return null; },
});
},
},
component: {
devServer: { framework: 'react', bundler: 'vite' },
},
env: {
apiUrl: 'http://localhost:4000/api',
},
});
API Cypress — Commandes Essentielles
Navigation et Interaction
describe('Page d\'accueil', () => {
beforeEach(() => {
cy.visit('/');
});
it('affiche le titre', () => {
cy.get('h1').should('contain', 'Bienvenue');
cy.title().should('eq', 'Mon App');
});
it('soumet le formulaire de connexion', () => {
cy.get('[data-cy=email]').type('user@example.com');
cy.get('[data-cy=password]').type('password123');
cy.get('[data-cy=submit]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-cy=welcome]').should('be.visible');
});
});
Sélecteurs — Toujours utiliser data-cy
<button data-cy="submit-btn" class="btn btn-primary">
Valider
</button>
cy.get('[data-cy=submit-btn]').click();
cy.get('.btn.btn-primary').click();
cy.contains('Valider').click();
Assertions (Chaînes Should)
cy.get('.alerte')
.should('be.visible')
.and('have.class', 'alerte-erreur')
.and('contain', 'Erreur de connexion');
cy.get('button').should('be.disabled');
cy.get('input').should('be.focused');
cy.get('.spinner').should('not.exist');
cy.get('li').should('have.length', 5);
cy.get('li').its('length').should('be.gte', 1);
Aliases et Requêtes Interdépendantes
it('crée puis modifie un élément', () => {
cy.get('[data-cy=user-list]').as('userList');
cy.get('@userList').should('contain', 'Alice');
cy.intercept('GET', '/api/users').as('getUsers');
cy.wait('@getUsers').its('response.statusCode').should('eq', 200);
});
Mocks et Interceptions Réseau
it('affiche les utilisateurs depuis l\'API mockée', () => {
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
}).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-cy=user-item]').should('have.length', 2);
});
it('teste la gestion d\'erreur 500', () => {
cy.intercept('POST', '/api/login', {
statusCode: 500,
body: { error: 'Serveur indisponible' },
});
cy.get('[data-cy=email]').type('test@test.com');
cy.get('[data-cy=password]').type('test');
cy.get('[data-cy=submit]').click();
cy.get().()
.(, );
});
(, {
cy.(, ).();
cy.().( {
(interception..)...({
: ,
: ,
});
});
});
cy.(, , {
req.( {
res.();
res.({ : });
});
});
Tests de Composants
import { mount } from 'cypress/react';
import Bouton from './Bouton';
it('affiche le libellé', () => {
mount(<Bouton label="Cliquez" />);
cy.get('button').should('contain', 'Cliquez');
});
it('émet un événement click', () => {
const onClick = cy.spy().as('clickSpy');
mount(<Bouton label="Go" onClick={onClick} />);
cy.get('button').click();
cy.get('@clickSpy').should('have.been.calledOnce');
});
Commandes Personnalisées
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session([email, password], () => {
cy.visit('/login');
cy.get('[data-cy=email]').type(email);
cy.get('[data-cy=password]').type(password);
cy.get('[data-cy=submit]').click();
cy.url().should('contain', '/dashboard');
});
});
beforeEach(() => {
cy.login('admin@test.com', 'secret');
});
Plugins et Tasks
import { defineConfig } from 'cypress';
import * as fs from 'fs';
export default defineConfig({
e2e: {
setupNodeEvents(on) {
on('task', {
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8');
}
return null;
},
});
},
},
});
CI/CD — GitHub Actions
name: Cypress Tests
on: [push]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- uses: cypress-io/github-action@v6
with:
build: npm run build
start: npm start
wait-on: 'http://localhost:3000'
browser: chrome
record: true
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
npx cypress run --browser chrome --headed --spec 'cypress/e2e/**/*.cy.ts'
npx cypress run --record --key <record-key>
npx cypress run --parallel
Bonnes Pratiques
- Utiliser
data-cy plutôt que CSS/texte pour les sélecteurs
cy.session() pour le login — met en cache le cookie/session entre tests
- Un test = un comportement — pas de longs scénarios tout-en-un
- Intercepter les appels API — ne pas dépendre d'un backend réel en CI
- Ne pas chaîner plus de 5-7 commandes — extraire en commande personnalisée
- Éviter
cy.wait(ms) — préférer cy.wait('@alias') ou assertions
- Tester ce que l'utilisateur voit — pas l'implémentation interne
Common Pitfalls
- Tests qui échouent sur CI mais pas en local — souvent un timeout (
defaultCommandTimeout: 10000)
cy.visit() sans baseUrl — fournir l'URL complète ou configurer baseUrl
- Mélanger tests E2E et composants — deux dossiers séparés (
e2e/ et component/)
- Sélecteurs trop larges —
cy.get('div') attrape trop d'éléments, être spécifique
- Ignorer les requêtes non interceptées —
cy.intercept('GET', '/api/**', ...).as('every')
Verification Checklist