What makes Cypress differentCe qui rend Cypress différent

Most E2E tools drive a browser from the outside — they send commands over a protocol and wait for responses. Cypress runs inside the browser, in the same execution context as the application. This architectural choice is what makes it fast, reliable, and debuggable in a way that Selenium-based tools simply aren't. La plupart des outils E2E pilotent un navigateur depuis l'extérieur — ils envoient des commandes via un protocole et attendent des réponses. Cypress tourne à l'intérieur du navigateur, dans le même contexte d'exécution que l'application. Ce choix architectural le rend rapide, fiable et débogable d'une façon que les outils basés sur Selenium ne permettent tout simplement pas.

The result: no more sleep() calls, no more flaky timeouts, and a time-travel debugger that shows a snapshot of the DOM at every command. Le résultat : plus d'appels sleep(), plus de timeouts instables, et un débogueur time-travel qui montre un snapshot du DOM à chaque commande.

Project structureStructure du projet

Cypress has an opinionated folder structure. Respecting it keeps tests organized and makes onboarding new team members straightforward. Cypress a une structure de dossiers imposée. La respecter garde les tests organisés et facilite l'intégration de nouveaux membres dans l'équipe.

project layout
cypress/
├── e2e/
│   ├── login.cy.js
│   └── dashboard.cy.js
├── fixtures/
│   └── users.json          // test data
├── support/
│   ├── commands.js         // custom cy.* commands
│   └── e2e.js              // global hooks / imports
└── cypress.config.js       // baseUrl, timeouts, env vars

Writing a real testÉcrire un vrai test

Cypress tests read like plain English. Commands are chained, assertions are built in, and the automatic waiting means you never have to think about timing. Les tests Cypress se lisent comme de l'anglais courant. Les commandes se chaînent, les assertions sont intégrées, et l'attente automatique signifie que tu n'as jamais à penser au timing.

cypress/e2e/login.cy.js
// Load test data from fixture
import users from '../fixtures/users.json';

describe('Login page', () => {

  beforeEach(() => {
    cy.visit('/login');            // baseUrl from config
  });

  it('logs in with valid credentials', () => {
    cy.get('#username').type(users.valid.email);
    cy.get('#password').type(users.valid.password);
    cy.get('[data-cy="submit"]').click();

    cy.url().should('include', '/dashboard');
    cy.get('[data-cy="welcome"]').should('be.visible');
  });

  it('shows error on wrong password', () => {
    cy.get('#username').type(users.valid.email);
    cy.get('#password').type('wrong-password');
    cy.get('[data-cy="submit"]').click();

    cy.get('[data-cy="error"]')
      .should('be.visible')
      .and('contain', 'Invalid credentials');
  });
});

💡 Best practice : always use data-cy attributes as selectors. They are immune to CSS changes, class renames, and refactoring — the test never breaks because a designer changed a class name. 💡 Bonne pratique : toujours utiliser les attributs data-cy comme sélecteurs. Ils sont immunisés contre les changements CSS, les renommages de classes et les refactorisations — le test ne casse jamais parce qu'un designer a changé un nom de classe.

Network interception with cy.interceptInterception réseau avec cy.intercept

One of Cypress's most powerful features is the ability to intercept, inspect, and stub HTTP requests — without a proxy, without a separate mock server, just one line of code. L'une des fonctionnalités les plus puissantes de Cypress est la capacité d'intercepter, inspecter et stubber les requêtes HTTP — sans proxy, sans serveur mock séparé, juste une ligne de code.

network interception
it('loads user list from API', () => {
  // Intercept the real API call
  cy.intercept('GET', '/api/users').as('getUsers');

  cy.visit('/users');
  cy.wait('@getUsers').its('response.statusCode').should('eq', 200);
  cy.get('[data-cy="user-row"]').should('have.length.gt', 0);
});

it('shows empty state when API returns no users', () => {
  // Stub the response to control the test scenario
  cy.intercept('GET', '/api/users', { body: [] }).as('emptyUsers');

  cy.visit('/users');
  cy.wait('@emptyUsers');
  cy.get('[data-cy="empty-state"]').should('be.visible');
});

Custom commandsCommandes personnalisées

Repetitive sequences — like logging in before every test — should become custom commands. They live in support/commands.js and are available globally as cy.*. Les séquences répétitives — comme se connecter avant chaque test — doivent devenir des commandes personnalisées. Elles vivent dans support/commands.js et sont disponibles globalement en tant que cy.*.

cypress/support/commands.js
// Reusable login via API — bypasses the UI, much faster
Cypress.Commands.add('loginByApi', (email, password) => {
  cy.request({
    method: 'POST',
    url:    '/api/auth/login',
    body:   { email, password },
  }).then(({ body }) => {
    window.localStorage.setItem('token', body.token);
  });
});

// Usage in any test:
// cy.loginByApi('user@example.com', 'pass123');

Cypress vs Selenium — honest comparisonCypress vs Selenium — comparaison honnête

✓ Cypress strengths✓ Points forts Cypress

  • Automatic waiting — no flaky timeoutsAttente automatique — pas de timeout instable
  • 🔍 Time-travel debuggerDébogueur time-travel
  • 🌐 Built-in network interceptionInterception réseau intégrée
  • 📸 Auto screenshots & videos on failureScreenshots et vidéos auto en échec
  • 🚀 Fast setup, zero config to startSetup rapide, zéro config pour démarrer

✗ Known limitations✗ Limitations connues

  • 🪟 No multi-tab / multi-window supportPas de multi-onglets / multi-fenêtres
  • 🌍 Limited cross-browser (mainly Chrome-based)Cross-browser limité (surtout Chrome)
  • 📱 No native mobile app testingPas de test d'app mobile native
  • 🖥️ Web only — no desktop appsWeb uniquement — pas d'apps desktop

CI integration with GitHub ActionsIntégration CI avec GitHub Actions

.github/workflows/cypress.yml
name: Cypress E2E
on: [push, pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: cypress-io/github-action@v6
        with:
          build: npm run build
          start: npm start
          wait-on: 'http://localhost:3000'
        env:
          CYPRESS_BASE_URL: ${{ secrets.BASE_URL }}

The official cypress-io/github-action handles caching, browser installation, and artifact upload automatically. Failed test videos and screenshots are available directly in the Actions summary. L'action officielle cypress-io/github-action gère le caching, l'installation du navigateur et l'upload des artefacts automatiquement. Les vidéos et screenshots des tests en échec sont disponibles directement dans le résumé Actions.

Key takeawaysCe qu'il faut retenir

  • Never use cy.wait(ms)Ne jamais utiliser cy.wait(ms)
    Always wait on an alias (cy.wait('@alias')) or an assertion. Fixed waits are the root cause of 90% of flaky Cypress tests. Toujours attendre sur un alias (cy.wait('@alias')) ou une assertion. Les attentes fixes sont la cause de 90% des tests Cypress instables.
  • 🏷️
    data-cy selectors are mandatoryLes sélecteurs data-cy sont obligatoires
    Agree on this convention with your dev team from day one. Retrofitting is painful. Se mettre d'accord sur cette convention avec l'équipe dev dès le premier jour. Le rétrofit est douloureux.
  • 🔁
    Login via API, not UILogin via API, pas via l'UI
    UI login in a beforeEach is slow. A direct API call that sets the session token is 10× faster. Le login UI dans un beforeEach est lent. Un appel API direct qui pose le token de session est 10× plus rapide.
  • 🌐
    Stub external APIsStubber les APIs externes
    Third-party services are outside your control. Intercept and stub them so your tests never depend on an external service being up. Les services tiers sont hors de ton contrôle. Les intercepter et les stubber pour que tes tests ne dépendent jamais de la disponibilité d'un service externe.