DevOps

Playwright et GitHub Actions : le guide CI/CD complet

Workflow complet, déclencheurs, secrets, sharding avec rapport unique et analyse des échecs : le guide pour faire tourner Playwright dans GitHub Actions.

Un test Playwright qui ne tourne que sur ton poste ne protège personne. Sa valeur apparaît le jour où il se lance tout seul, à chaque pull request, et bloque une régression avant qu'elle n'arrive en recette. GitHub Actions est le moyen le plus simple d'y arriver : le service est gratuit pour les dépôts publics, intégré à GitHub, et Playwright fournit déjà tout ce qu'il faut pour s'y brancher.

Ce guide part d'un projet Playwright qui fonctionne en local et va jusqu'à un pipeline de production : premier workflow, déclencheurs, secrets, parallélisation sur plusieurs machines avec un rapport unique, et analyse des échecs. Si tu te demandes d'abord pourquoi et quand lancer quels tests, commence par Playwright et CI/CD : faire tourner ses tests à chaque commit. Ici, on passe à la pratique.

Étape 1 : préparer le projet pour la CI

Avant d'écrire le moindre fichier YAML, ta configuration doit se comporter différemment selon qu'elle tourne sur ton poste ou en CI. GitHub Actions définit automatiquement la variable d'environnement CI, c'est elle qu'on utilise.

ts
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  ],
});

Ce que chaque ligne apporte en CI :

  • forbidOnly fait échouer le pipeline si un test.only a été oublié dans le code. Sans ça, un seul test tourne et le pipeline reste vert.
  • retries: 2 relance un test en échec deux fois. Un test qui ne passe qu'au deuxième essai est marqué flaky dans le rapport : c'est un signal à traiter, pas une réussite.
  • workers: 1 privilégie la stabilité sur les petites machines de GitHub. Tu pourras l'augmenter une fois la suite fiable.
  • trace: 'on-first-retry' enregistre une trace complète (DOM, réseau, console) uniquement quand un test est relancé. C'est l'outil qui te fera gagner le plus de temps pour comprendre un échec.
  • baseURL évite toute URL en dur : en CI, on la fournira par variable d'environnement.

Deux règles de conception comptent autant que la configuration : chaque test crée ses propres données (idéalement par l'API, voir automatiser vos tests API avec Playwright), et aucun test ne dépend de l'ordre d'exécution. En CI, cet ordre n'est jamais celui de ton poste.

Étape 2 : le premier workflow

Crée le fichier .github/workflows/playwright.yml à la racine du dépôt :

yaml
name: Tests Playwright

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm

      - name: Installer les dépendances
        run: npm ci

      - name: Installer les navigateurs Playwright
        run: npx playwright install --with-deps chromium

      - name: Lancer les tests
        run: npx playwright test

      - name: Publier le rapport
        uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Les points qui font la différence :

  • npm ci installe exactement les versions du package-lock.json. N'utilise pas npm install en CI : il peut modifier les versions et rendre un échec impossible à reproduire.
  • cache: npm met en cache les paquets npm, ce qui accélère l'installation.
  • npx playwright install --with-deps chromium installe le navigateur et les bibliothèques système dont il a besoin. Si tu testes aussi Firefox et WebKit, retire chromium pour tout installer.
  • if: ${{ !cancelled() }} publie le rapport même quand les tests échouent. C'est justement dans ce cas que tu en as besoin. Avec if: failure(), tu n'aurais rien les jours où tout passe, et donc aucun historique.
  • timeout-minutes: 30 coupe un job bloqué. Sans cette ligne, GitHub attend jusqu'à six heures et consomme tes minutes.
💡
Ne mets pas les navigateurs Playwright en cache avec actions/cache. La documentation Playwright le déconseille : restaurer le cache prend à peu près autant de temps que télécharger les navigateurs, et les dépendances système doivent de toute façon être réinstallées à chaque exécution.

Pousse le fichier : l'onglet Actions de ton dépôt affiche l'exécution. Le rapport HTML se télécharge en bas de la page de l'exécution, dans la section Artifacts.

Étape 3 : choisir les bons déclencheurs

Lancer toute la suite à chaque push sur chaque branche coûte cher et ralentit tout le monde. Voici une configuration plus réaliste :

yaml
on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 3 * * 1-5'
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
DéclencheurQuandUsage
pull_requestÀ chaque pull requestBloquer une régression avant le merge
push sur mainAprès chaque mergeVérifier l'état réel de la branche principale
scheduleChaque nuit de semaineSuite complète, tous navigateurs
workflow_dispatchÀ la demandeBouton « Run workflow » dans l'onglet Actions

Le bloc concurrency annule l'exécution en cours quand tu pousses un nouveau commit sur la même branche : inutile de finir de tester un code déjà remplacé. Attention, l'heure du cron est en UTC : 0 3 correspond à 4 h ou 5 h du matin en France selon la saison.

Pour que les tests bloquent réellement le merge, active la protection de branche : Settings → Branches → Branch protection rules, puis coche le job test dans les vérifications obligatoires.

Étape 4 : gérer les URL, comptes et secrets

Tes tests ont besoin d'une URL d'environnement et souvent d'un compte de test. Ces valeurs ne doivent jamais apparaître dans le code. GitHub propose deux mécanismes :

  • les variables (vars) pour les valeurs non sensibles, comme l'URL de staging ;
  • les secrets (secrets) pour les identifiants, chiffrés et masqués dans les logs.

Tu les crées dans Settings → Secrets and variables → Actions, puis tu les injectes dans le job :

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    environment: staging
    env:
      BASE_URL: ${{ vars.BASE_URL }}
      TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
      TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}

Dans tes tests, tu lis simplement process.env.TEST_USER_EMAIL. La ligne environment: staging rattache le job à un environnement GitHub : tu peux ainsi définir des valeurs différentes pour staging et préproduction, et même exiger une validation manuelle avant de lancer les tests sur un environnement sensible.

Un piège classique : les secrets ne sont pas transmis aux pull requests ouvertes depuis un fork. Si un contributeur externe propose une modification, les tests qui ont besoin d'un compte échoueront chez lui. C'est une protection volontaire de GitHub.

Étape 5 : paralléliser avec le sharding

Quand la suite dépasse dix à quinze minutes, l'équipe arrête d'attendre le résultat. Le sharding découpe la suite en plusieurs parts exécutées sur des machines différentes, en même temps.

Le piège : chaque machine produit son propre rapport, qui ne contient qu'une partie des tests. Il faut donc demander à chaque shard un rapport intermédiaire au format blob, puis les fusionner dans un dernier job. Commence par adapter le reporter dans la configuration :

ts
reporter: process.env.CI ? [['blob'], ['github']] : 'list',

Puis le workflow :

yaml
jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps
      - name: Lancer le shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
        run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: blob-report-${{ matrix.shardIndex }}
          path: blob-report
          retention-days: 1

  merge-reports:
    if: ${{ !cancelled() }}
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - uses: actions/download-artifact@v4
        with:
          path: all-blob-reports
          pattern: blob-report-*
          merge-multiple: true
      - name: Fusionner les rapports
        run: npx playwright merge-reports --reporter html ./all-blob-reports
      - uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report
          retention-days: 14

Trois réglages sont indispensables :

  • fail-fast: false : par défaut, GitHub annule tous les shards dès que l'un d'eux échoue. Tu perdrais alors les résultats des trois autres.
  • fullyParallel: true dans la configuration (déjà présent à l'étape 1) : Playwright répartit alors les tests entre les shards, et pas seulement les fichiers. Sans ça, un gros fichier de tests déséquilibre tout le découpage.
  • if: ${{ !cancelled() }} sur le job de fusion : il doit tourner même si des tests ont échoué, sinon tu n'as pas de rapport.

Quatre shards ne divisent pas exactement le temps par quatre : chaque machine doit encore installer les dépendances et les navigateurs, soit environ une à deux minutes. Au-delà de quatre à six shards, le gain devient faible pour une suite de taille moyenne.

Étape 6 : tester sur plusieurs navigateurs

Déclare les navigateurs comme des projets dans la configuration :

ts
projects: [
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  { name: 'webkit', use: { ...devices['Desktop Safari'] } },
],

Sans autre changement, npx playwright test lance la suite trois fois, une par navigateur. C'est simple, mais le temps est triplé. Une bonne répartition : Chromium seul sur les pull requests (npx playwright test --project=chromium), et les trois navigateurs dans l'exécution planifiée de la nuit.

Tu peux aussi combiner navigateurs et shards dans la matrice. Pense alors à donner un nom unique à chaque rapport intermédiaire, sinon les fichiers s'écrasent :

yaml
strategy:
  fail-fast: false
  matrix:
    project: [chromium, firefox, webkit]
    shardIndex: [1, 2]
    shardTotal: [2]
steps:
  - run: npx playwright test --project=${{ matrix.project }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
  - uses: actions/upload-artifact@v4
    if: ${{ !cancelled() }}
    with:
      name: blob-report-${{ matrix.project }}-${{ matrix.shardIndex }}
      path: blob-report
      retention-days: 1

Cette matrice crée six jobs en parallèle. Sur un dépôt privé, chacun consomme tes minutes GitHub Actions : garde-la pour la nuit.

Étape 7 : comprendre un échec sans le reproduire en local

Un pipeline rouge n'est utile que si tu trouves la cause rapidement. Playwright te donne trois niveaux d'information.

1. Les annotations dans la pull request. Le reporter github affiche chaque échec directement sur la ligne de code concernée, dans l'onglet Files changed de la pull request. Tu vois le test en échec sans même ouvrir les logs.

2. Le rapport HTML. Télécharge l'artefact playwright-report, décompresse-le, puis ouvre-le :

bash
npx playwright show-report playwright-report

Chaque test en échec affiche le message d'erreur, la ligne concernée et la capture d'écran prise au moment de l'échec.

3. La trace. Pour les tests relancés, le rapport contient une trace. Elle permet de rejouer l'exécution action par action, avec l'état du DOM, les requêtes réseau et les messages de la console à chaque étape. Tu peux l'ouvrir depuis le rapport, ou glisser le fichier trace.zip sur trace.playwright.dev, sans rien installer.

Dans la grande majorité des cas, la trace suffit à distinguer un vrai bug de l'application d'un test fragile : un sélecteur trop dépendant de la mise en page, une donnée manquante, ou une attente mal placée.

Option : exécuter les tests dans l'image Docker officielle

Microsoft publie une image Docker qui contient déjà Node.js, les navigateurs et toutes leurs dépendances. Elle évite l'étape playwright install et garantit un environnement identique d'une exécution à l'autre :

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.55.1-noble
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test

La version de l'image doit correspondre exactement à la version de @playwright/test dans ton package.json. Remplace v1.55.1 par la tienne, et pense à la mettre à jour à chaque montée de version de Playwright. Sinon, les tests échouent en indiquant que les navigateurs sont introuvables.

Les erreurs les plus fréquentes

ProblèmeSymptômeCause et solution
Tests verts en local, rouges en CIÉchecs aléatoires ou sur des textesURL, fuseau horaire ou langue différents : fixe locale et timezoneId dans use
Executable doesn't existAucun test ne démarreÉtape playwright install oubliée, ou version de l'image Docker différente de @playwright/test
Rapport incompletSeule une partie des tests apparaîtSharding sans reporter blob ni job de fusion
Variables videsÉchec à la connexionSecret mal nommé, ou pull request ouverte depuis un fork
Pipeline toujours vertUn seul test exécutétest.only oublié : active forbidOnly
Suite de plus en plus lentePlus de 20 minutesTrop de tests par l'interface : déplace les vérifications métier vers des tests API

Une dernière erreur, moins visible : augmenter retries jusqu'à ce que le pipeline passe. Les relances absorbent un aléa réseau ponctuel. Si un test a besoin de trois essais pour réussir, il est mal écrit, et il finira par masquer un vrai bug. Consulte régulièrement la liste des tests flaky du rapport, et corrige-les ou supprime-les.

FAQ sur Playwright et GitHub Actions

GitHub Actions est-il gratuit pour lancer des tests Playwright ?

Oui pour les dépôts publics, sans limite de minutes sur les machines standard. Les dépôts privés disposent d'un quota mensuel de minutes gratuites selon ton offre GitHub, au-delà duquel l'utilisation est facturée. Le sharding et les matrices multi-navigateurs multiplient la consommation : réserve-les aux exécutions qui en ont vraiment besoin.

Faut-il lancer tous les tests à chaque pull request ?

Pas forcément. Sur une petite suite (moins de 10 minutes), oui : c'est le plus simple et le plus sûr. Au-delà, lance un sous-ensemble critique sur les pull requests, par exemple les tests marqués d'un tag @smoke avec npx playwright test --grep @smoke, puis la suite complète après le merge et chaque nuit.

Comment tester une application qui doit d'abord être démarrée ?

Utilise l'option webServer de la configuration Playwright. Elle lance ta commande de démarrage (par exemple npm run start), attend que l'URL réponde, exécute les tests, puis arrête le serveur. Ça fonctionne de la même façon en local et en CI, sans étape supplémentaire dans le workflow.

Ce workflow fonctionne-t-il avec GitLab CI ou Jenkins ?

Le principe est identique : installer les dépendances, installer les navigateurs, lancer les tests, publier le rapport. Seule la syntaxe du fichier de pipeline change. La configuration Playwright, le sharding avec --shard et la fusion avec merge-reports restent les mêmes. L'image Docker officielle est d'ailleurs le moyen le plus simple de démarrer sur ces outils.

Pour aller plus loin

Un pipeline solide repose sur une suite de tests bien structurée. Si tu débutes, commence par débuter avec Playwright, puis organise ton code avec le Page Object Model avant de multiplier les tests en CI.

Mettre en place ce pipeline sur un vrai projet, avec quelqu'un qui relit ta configuration et t'aide à analyser tes premiers échecs, c'est exactement ce que propose l'accompagnement Mission Playwright.