Cypress
Le framework de test end-to-end moderne qui permet de créer des tests fiables et rapides pour les applications web, avec une expérience développeur exceptionnelle et un système de debugging visuel puissant.
Qu'est-ce que Cypress ?
Dans le monde des sites web et des applications, comment être sûr que tout fonctionne correctement avant de livrer aux utilisateurs ? C'est là qu'intervient Cypress - un outil qui permet de créer facilement des "robots testeurs" qui vont vérifier automatiquement que votre site web fonctionne comme prévu.
Imaginez Cypress comme un assistant virtuel ultra rapide qui peut, en quelques secondes, parcourir votre site web, remplir des formulaires, cliquer sur des boutons, et vérifier que tout s'affiche correctement - exactement comme le ferait un utilisateur réel, mais de manière automatisée et beaucoup plus rapide.
Ce que Cypress apporte à votre projet
Confiance dans les livraisons
La certitude que vos nouvelles fonctionnalités n'ont pas cassé les fonctionnalités existantes, permettant de livrer de nouvelles versions plus rapidement et en toute sécurité.
Détection rapide des bugs
Identification immédiate des problèmes pendant le développement, avant même qu'ils n'atteignent les utilisateurs finaux, réduisant les coûts de correction.
Gain de temps et d'argent
Automatisation des tests répétitifs qui prendraient des heures à réaliser manuellement, libérant les équipes pour des tâches à plus forte valeur ajoutée.
Meilleure expérience utilisateur
Garantie que les fonctionnalités critiques comme les formulaires, les paniers d'achat ou les processus d'inscription fonctionnent parfaitement pour tous les utilisateurs.
En résumé, Cypress est comme une police d'assurance pour votre site web ou application - il vérifie en permanence que tout fonctionne correctement, vous alertant immédiatement si quelque chose ne va pas, et vous donnant ainsi la tranquillité d'esprit nécessaire pour améliorer et faire évoluer votre produit en toute confiance.
Fonctionnement technique
Cypress est un framework de test end-to-end basé sur
JavaScript qui s'exécute directement dans le navigateur. Contrairement à d'autres outils de test qui opèrent via des pilotes de navigateur externes, Cypress fonctionne dans le même cycle d'exécution que l'application testée, offrant ainsi un accès sans précédent à tous les objets de l'application.
Architecture et concepts clés
Cypress possède une architecture unique qui lui confère plusieurs avantages par rapport aux frameworks de test traditionnels :
- Exécution dans le navigateur - Cypress s'exécute directement dans le même contexte que l'application testée, permettant un accès direct au DOM, aux objets window, document, etc.
- Contrôle du réseau - Cypress peut intercepter, stubber et contrôler le trafic réseau, facilitant les tests avec des données simulées
- Architecture asynchrone - Toutes les commandes Cypress s'exécutent de manière asynchrone avec des attentes automatiques intelligentes
- Time Travel - L'interface de Cypress permet de voir l'état de l'application à chaque étape du test
- Debugging visuel - Les instantanés avant/après chaque action permettent un débogage immédiat
Exemples pratiques
Test d'authentification de base
Voici un exemple simple de test Cypress qui vérifie le processus de connexion d'un utilisateur :
// cypress/e2e/authentification.cy.js
describe('Authentification', () => {
beforeEach(() => {
// Visite la page d'accueil avant chaque test
cy.visit('/');
});
it('Permet à un utilisateur de se connecter avec des identifiants valides', () => {
// Clique sur le bouton de connexion
cy.contains('Connexion').click();
// Vérifie qu'on est bien sur la page de login
cy.url().should('include', '/login');
// Remplit le formulaire de connexion
cy.get('input[name="email"]').type('utilisateur@exemple.fr');
cy.get('input[name="password"]').type('motdepasse123{enter}');
// Vérifie que l'utilisateur est bien connecté
cy.url().should('include', '/tableau-de-bord');
cy.contains('Bienvenue, utilisateur@exemple.fr').should('be.visible');
// Vérifie que le menu utilisateur est affiché
cy.get('.user-menu').should('exist');
});
it('Affiche un message d'erreur avec des identifiants invalides', () => {
// Clique sur le bouton de connexion
cy.contains('Connexion').click();
// Remplit le formulaire avec des identifiants incorrects
cy.get('input[name="email"]').type('utilisateur@exemple.fr');
cy.get('input[name="password"]').type('mauvais_mot_de_passe{enter}');
// Vérifie que l'erreur est affichée
cy.contains('Identifiants invalides').should('be.visible');
// Vérifie qu'on reste sur la page de login
cy.url().should('include', '/login');
});
});
Commandes personnalisées
L'une des forces de Cypress est la possibilité de créer des commandes personnalisées pour factoriser les opérations courantes :
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
// Cette commande personnalisée permet de se connecter rapidement
cy.session([email, password], () => {
cy.visit('/login');
cy.get('input[name="email"]').type(email);
cy.get('input[name="password"]').type(password);
cy.get('form').submit();
cy.url().should('include', '/tableau-de-bord');
});
});
Cypress.Commands.add('createProduct', (productData) => {
// Cette commande crée un produit via l'API
cy.request({
method: 'POST',
url: '/api/products',
body: productData,
headers: {
'Authorization': `Bearer ${Cypress.env('API_TOKEN')}`
}
}).then((response) => {
expect(response.status).to.eq(201);
return response.body.id; // Retourne l'ID du produit créé
});
});
Cypress.Commands.add('deleteProduct', (productId) => {
// Cette commande supprime un produit via l'API
cy.request({
method: 'DELETE',
url: `/api/products/${productId}`,
headers: {
'Authorization': `Bearer ${Cypress.env('API_TOKEN')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
});
});
// Usage dans les tests:
// cy.login('utilisateur@exemple.fr', 'motdepasse123');
// cy.createProduct({ name: 'Test Product', price: 19.99 }).then(productId => {
// // faire quelque chose avec le produit
// cy.deleteProduct(productId);
// });
Fixtures et interception réseau
Cypress peut simuler des réponses API grâce aux fixtures et à l'interception des requêtes réseau :
// Exemple de fixture: cypress/fixtures/products.json
{
"products": [
{
"id": 1,
"name": "Smartphone XYZ",
"price": 599.99,
"category": "Électronique",
"inStock": true,
"imageUrl": "/images/products/smartphone-xyz.jpg"
},
{
"id": 2,
"name": "Casque Audio Premium",
"price": 199.99,
"category": "Accessoires",
"inStock": true,
"imageUrl": "/images/products/casque-premium.jpg"
},
{
"id": 3,
"name": "Tablette Ultra",
"price": 399.99,
"category": "Électronique",
"inStock": false,
"imageUrl": "/images/products/tablette-ultra.jpg"
}
],
"totalCount": 3
}
// Utilisation dans les tests: cypress/e2e/produits.cy.js
describe('Catalogue de produits', () => {
beforeEach(() => {
// Intercepter la requête API et la remplacer par notre fixture
cy.intercept('GET', '/api/products*', { fixture: 'products.json' }).as('getProducts');
cy.visit('/products');
cy.wait('@getProducts');
});
it('Affiche la liste des produits correctement', () => {
// Vérifie que tous les produits de la fixture sont affichés
cy.get('.product-card').should('have.length', 3);
cy.contains('Smartphone XYZ').should('be.visible');
cy.contains('Casque Audio Premium').should('be.visible');
cy.contains('Tablette Ultra').should('be.visible');
});
it('Affiche correctement les produits en rupture de stock', () => {
// Vérifie que les produits en rupture sont marqués comme tels
cy.contains('.product-card', 'Tablette Ultra')
.should('contain', 'Rupture de stock')
.and('have.class', 'out-of-stock');
});
it('Permet de filtrer les produits par catégorie', () => {
// Simuler une autre réponse d'API pour le filtre
cy.intercept('GET', '/api/products?category=Électronique', {
fixture: 'filtered-products.json'
}).as('getFilteredProducts');
// Cliquer sur le filtre de catégorie
cy.get('select[name="category"]').select('Électronique');
cy.wait('@getFilteredProducts');
// Vérifier que les résultats filtrés sont bien affichés
cy.get('.product-card').should('have.length', 2);
cy.contains('Smartphone XYZ').should('be.visible');
cy.contains('Tablette Ultra').should('be.visible');
cy.contains('Casque Audio Premium').should('not.exist');
});
});
Tests de composants
Depuis la version 10, Cypress permet également de tester des composants isolés React, Vue ou Angular :
// Exemple de test de composant React avec Cypress
// cypress/component/Button.cy.jsx
import Button from '../../src/components/Button';
describe('Button Component', () => {
it('Affiche le texte du bouton correctement', () => {
// Monte le composant
cy.mount(<Button>Cliquez-moi</Button>);
// Vérifie que le texte est affiché
cy.get('button').should('have.text', 'Cliquez-moi');
});
it('Appelle le callback onClick quand on clique sur le bouton', () => {
// Créer un spy pour le onClick
const onClickSpy = cy.spy().as('onClickSpy');
// Monte le composant avec le spy
cy.mount(<Button onClick={onClickSpy}>Cliquez-moi</Button>);
// Clique sur le bouton
cy.get('button').click();
// Vérifie que le spy a été appelé
cy.get('@onClickSpy').should('have.been.calledOnce');
});
it('Applique la classe "disabled" quand la prop disabled est true', () => {
// Monte le composant avec prop disabled
cy.mount(<Button disabled>Désactivé</Button>);
// Vérifie que la classe est appliquée
cy.get('button')
.should('have.class', 'disabled')
.and('have.attr', 'disabled');
});
it('Applique les styles variant correctement', () => {
// Monte le composant avec variant primary
cy.mount(<Button variant="primary">Primaire</Button>);
// Vérifie la classe de style
cy.get('button').should('have.class', 'btn-primary');
// Monte le composant avec variant secondary
cy.mount(<Button variant="secondary">Secondaire</Button>);
// Vérifie la classe de style
cy.get('button').should('have.class', 'btn-secondary');
});
});
Configuration et bonnes pratiques
La configuration de Cypress se fait principalement via le fichier cypress.config.js
(ou cypress.config.ts
pour TypeScript) :
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 5000,
screenshotOnFailure: true,
video: true,
retries: {
runMode: 2,
openMode: 0
},
setupNodeEvents(on, config) {
// plugins et hooks peuvent être configurés ici
},
},
component: {
devServer: {
framework: 'react',
bundler: 'webpack',
},
},
})
Voici quelques bonnes pratiques pour utiliser Cypress efficacement :
- Tests isolés - Chaque test doit pouvoir s'exécuter indépendamment des autres
- Données de test propres - Utilisez
cy.intercept()
et des fixtures pour contrôler l'état de votre application - Sélecteurs robustes - Utilisez des attributs
data-cy
spécifiques plutôt que des classes CSS ou des sélecteurs complexes - Commandes personnalisées - Factoriser les opérations répétitives dans des commandes personnalisées
- Ne pas tester des aspects visuels précis - Cypress n'est pas destiné aux tests de pixel-perfect, mais plutôt aux comportements fonctionnels
- Utiliser cy.session() - Pour préserver l'état de session entre les tests et éviter de se reconnecter à chaque fois
- Ne pas abuser de cy.wait() - Préférer les attentes implicites comme
.should('be.visible')
Avantages et limitations
Avantages de Cypress
- Expérience développeur exceptionnelle - Interface visuelle, debugging en temps réel, time travel
- Tests plus fiables - Moins de tests flaky grâce aux attentes automatiques
- Vitesse d'exécution - Plus rapide que la plupart des frameworks E2E traditionnels
- Documentation excellente - Une des meilleures documentations dans l'écosystème des outils de test
- Écosystème riche - Nombreux plugins et intégrations disponibles
Limitations
- Support limité des navigateurs - Bien que amélioré récemment, ne supporte pas tous les navigateurs comme Safari
- Pas d'exécution cross-domain - Les tests sont limités à un seul domaine par test
- Pas de support natif pour plusieurs onglets - Les tests sont limités à un seul onglet
- Coût pour certaines fonctionnalités avancées - Cypress Dashboard payant pour les équipes au-delà d'un certain seuil
Cypress est un outil puissant qui a révolutionné la façon dont les développeurs créent et maintiennent des tests end-to-end. Sa facilité d'utilisation, son interface visuelle et sa capacité à créer des tests fiables en font un choix privilégié pour de nombreuses équipes de développement web modernes.
Cas d'usage
Applications React/NextJS
Tests end-to-end complets pour les applications React et
Next.js, couvrant les parcours utilisateurs critiques, le rendering des composants, et la gestion d'état globale.
Sites e-commerce
Vérification automatisée des parcours d'achat complets, depuis la navigation dans le catalogue jusqu'à la finalisation de la commande, en passant par les interactions avec le panier.
Tests de validation de formulaires
Vérification exhaustive du comportement des formulaires complexes, incluant la validation des champs, la gestion des erreurs et les soumissions réussies.
Applications SaaS
Tests des fonctionnalités critiques des applications SaaS, comme l'onboarding, la gestion des abonnements, les tableaux de bord et les fonctionnalités spécifiques aux différents plans.
Applications à forte interaction
Test des interfaces utilisateurs complexes comme les éditeurs de texte riches, les applications de dessin, les tableaux de données interactifs ou les dashboards avec drag-and-drop.
Intégration dans les pipelines CI/CD
Cypress s'intègre parfaitement dans les workflows d'intégration continue pour automatiser les tests à chaque commit ou pull request. Voici un exemple de configuration pour
GitHub Actions :
name: E2E Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Start server in background
run: npm run start & npx wait-on http://localhost:3000
- name: Run Cypress tests
uses: cypress-io/github-action@v5
with:
browser: chrome
record: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
- name: Upload videos
uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos
path: cypress/videos