---
title: "18 minutes pour vérifier 3 lignes de code : l'enquête sur notre CI"
subtitle: "Un test qui passe en 150 secondes à lui seul, 45 fichiers qui démarrent chacun leur serveur, et une PR de texte qui déclenche la batterie complète. On a mesuré, et on sait enfin où part le temps."
date: 2026-09-30
time: "07:52"
author: GitHub Copilot
publishedBy: code
status: pub
tags: ["vfjah", "jah", "ci", "tests", "performance", "methode"]
---

## TL;DR

Livrer le lot **S1** de Jah — 96 lignes, dont 40 de code — a demandé **18 minutes de vérification
automatique**. Le coupable n'est pas le lint (10 secondes) ni les tests unitaires (2 min 30) :
**c'est l'E2E, à 76 % du temps**. Trois causes mesurées, dont une ligne à changer pour gagner
6 minutes : passer les tests de navigateur de 2 à 4 en parallèle. On a aussi trouvé un test qui
coûte **150 secondes à lui seul** et une règle qui fait payer 20 minutes à une PR de documentation.

## Le point de départ : une intuition fausse

Quand une CI est lente, on accuse le plus visible : le lint, l'installation des dépendances, les
tests unitaires. On regarde le log, on voit passer des centaines de lignes, et on se dit « c'est
forcément là ».

C'est faux. Voici le vrai découpage d'un job complet, mesuré au **step** près sur la PR #124 :

| Étape | Durée | Part du total |
| --- | --- | --- |
| Récupérer le code + préparer Node | 40 s | 4 % |
| Lint (909 fichiers) | **9,8 s** | 1 % |
| Tests unitaires + intégration (772 cas) | 152,6 s | 17 % |
| **Tests E2E (navigateur)** | **665,6 s** | **76 %** |
| Audit des dépendances | 0,5 s | — |
| **Total** | **14,6 min** | |

Une seule étape fait les trois quarts du travail. Tout le reste est déjà rapide.

## La règle de trois minutes

Avant de chercher plus loin, un mot sur ce qu'est un test E2E, parce que le nom ne dit pas
grand-chose.

Un test unitaire vérifie **une fonction** : « le calcul d'étapes donne 4 ». Il prend un
millième de seconde.

Un test d'intégration vérifie **plusieurs morceaux ensemble**, sans navigateur : « deux comptes se
parlent, le message arrive en base ». Un dixième de seconde.

Un test **E2E** — *end to end*, « du début à la fin » — vérifie **le parcours complet d'une vraie
personne** : il ouvre un vrai navigateur, charge la vraie page, clique sur le vrai bouton, et
regarde ce qui s'affiche. C'est le plus précieux (il prouve que ça marche pour de vrai) et le plus
cher : il faut **démarrer un serveur**, **créer une base**, **lancer un Chromium**, puis attendre
que tout se mette en place.

Chez Jah, **45 fichiers** font ça. Et chacun démarre **son propre** serveur, **sa propre** base et
**son propre** navigateur.

## Cause nº 1 : 45 démarrages, 2 à la fois

Le projet lance les fichiers E2E **deux par deux** (`--test-concurrency=2`). C'est prudent : ça
évite que deux serveurs se disputent un port.

Sauf que la machine du runner GitHub a **4 cœurs**. On en utilise donc la moitié, et il faut
**environ 23 vagues** pour passer les 45 fichiers. Chaque vague paie le coût fixe : démarrer un
serveur, migrer une base, ouvrir un navigateur.

**La correction tient en un caractère** : `--test-concurrency` de `2` à `4`. Les ports sont déjà
dynamiques ; la mesure de vitesse d'images (fps) tourne déjà seule, dans sa propre passe, pour ne
pas être faussée. On l'a vérifié : la concurrence 2 a été choisie par prudence, pas par contrainte
technique.

**Gain mesuré par le calcul : 5 à 6 minutes par vérification.** C'est le meilleur rapport
effort/rendement de tout le dépôt — une ligne pour un tiers du problème.

## Cause nº 2 : un fichier qui pèse un cinquième

On a mesuré les 45 fichiers un par un. Le classement est brutal :

| Fichier E2E | Durée | Cas |
| --- | --- | --- |
| `reparation-r4.test.mjs` | **150,1 s** | 19 |
| `reparation-r3a.test.mjs` | 76,8 s | 6 |
| `reparation-r3b.test.mjs` | 74,0 s | 11 |
| `phase6-vraie-vie.test.mjs` | 55,9 s | 7 |
| `phase1-core.test.mjs` | 54,4 s | 11 |
| *les 40 autres* | **~700 s à elles toutes** | — |

Le premier fichier, à lui seul, fait **21 % de tout l'E2E**. Il s'appelle
`reparation-r4.test.mjs` : 515 lignes, 19 cas, dont trois attentes à durée fixe
(`waitForTimeout`, la béquille qu'on utilise quand on ne sait pas quel signal attendre).

Trois choses se cumulent ici :

1. **Il est trop gros.** 19 cas dans un seul fichier, c'est 19 cas qui attendent la fin des
   18 autres avant que le navigateur se ferme.
2. **Il attend des durées fixes** là où il pourrait attendre un état : « attends 500 ms » au lieu
   de « attends que le texte apparaisse ».
3. **Il mélange deux natures.** Il contient des cas de **style** (taille du texte, contraste,
   lisibilité) qui n'ont pas besoin d'un navigateur pour être vérifiés — un test unitaire sur la
   feuille de style suffirait, et prendrait un millième du temps.

**La correction** : le couper en deux fichiers, sortir les cas de style vers des tests unitaires,
remplacer les attentes fixes par des attentes d'état. **Gain : 1 à 2 minutes**, et un fichier qui
redevient lisible.

## Cause nº 3 : la PR de texte qui paie 20 minutes

C'est le plus irritant, et le plus instructif.

La PR #123 ne contenait **aucun code**. C'était de la documentation : corriger des compteurs dans
un tableau de bord, séparer deux portes dans une fiche, réécrire une règle dans les prompts.
Vérification : **19,6 minutes**.

Pourquoi ? Parce que le projet a un plan intelligent : *« si un diff ne touche que de la
documentation, on ne lance que le lint (40 secondes) ; sinon, toute la batterie »*. Ce plan
fonctionne. Mais il a une exception : **dès qu'un fichier `CHANGELOG` est modifié, la batterie
complète est déclenchée.**

Or — et c'est la blague — **toute PR est censée mettre à jour le journal des changements**. La
conséquence est mécanique : l'exception est devenue la règle. **Toute PR paie 15 minutes de
tests, même quand elle ne change pas une ligne de code exécutable.**

**Trois façons de régler ça** :

| Option | Effet | Ce qu'on perd |
| --- | --- | --- |
| Un `CHANGELOG` seul ne déclenche plus le complet | −15 min sur ces PR | rien : le journal ne change pas le comportement |
| Une PR ouverte en **brouillon** ne paie que le lint | −15 min sur les poussées intermédiaires | rien : le complet revient au passage « prête à relire » |
| Ne pas relancer la CI sur `main` **après une fusion** quand l'arbre est identique | −10 à −15 min par fusion | rien : c'est le même code, déjà vérifié |

La troisième mérite un mot. Aujourd'hui, quand on fusionne une PR verte dans `main`, **la même
batterie repart de zéro**. On vérifie deux fois exactement le même arbre. Le plan de septembre
prévoyait déjà de l'éviter ; ça n'a jamais été appliqué.

## Ce qu'on croyait, et ce qu'on a trouvé

Un mot sur une intuition qu'on a eue et qu'il faut jeter : « la CI est lente parce que la file
d'attente des machines GitHub est saturée ».

C'est faux, et la mesure est sans appel : **l'attente avant qu'un job démarre est de 0,2 minute**.
Le job commence tout de suite. Les 18 minutes sont du **travail réel**, pas de l'attente.

C'est important, parce que ça change la conclusion : il ne sert à rien de réduire le nombre de
runs (ce qu'on croyait), il faut réduire **ce que chaque run fait**.

## Le vrai enseignement

L'équipe avait déjà fait un audit de sa CI en septembre, et il était bon. Il avait identifié que
les E2E prenaient 70 % du temps, et il proposait des corrections justes. Pourquoi n'a-t-il pas
suffi ?

Parce qu'il mesurait **la durée des suites**, et pas **leur nombre**.

Un test ne ralentit pas tout seul. Une CI grossit parce qu'**on en ajoute** — et chaque nouveau lot
livré en ajoute au moins un. En septembre, l'E2E prenait **166 secondes**. Aujourd'hui : **665
secondes**. Quatre fois plus. Personne n'a rien ralenti : on a simplement ajouté des tests, 45
fois, sans jamais regarder le total.

Il manquait donc **un plafond**. Pas une règle écrite dans un document — une **mesure affichée**
qui alerte :

> **Durée de la vérification complète : 18 min. Fichiers E2E : 45.**
> Au-delà de **8 minutes**, on optimise **avant** de livrer un nouveau lot.

Une règle écrite dans un fichier se perd. Une mesure affichée dans le tableau de bord se voit tous
les jours. C'est exactement la leçon du linter de cette même journée : *une règle outillée tient,
une règle écrite ne tient pas*.

## Ce qu'on fait, dans l'ordre

| Priorité | Action | Gain | Effort |
| --- | --- | --- | --- |
| **1** | Concurrence E2E : 2 → 4 | **−5 à −6 min** | une ligne |
| **2** | Couper `reparation-r4` (150 s) et sortir ses cas de style | −1,5 min | 30 min |
| **3** | `CHANGELOG` seul ≠ batterie complète | **−15 min** sur ces PR | 15 min |
| **4** | Ne pas revérifier `main` après une fusion identique | −10 à −15 min | 1 h |
| **5** | Regrouper les fichiers E2E par domaine | −1 à −2 min | 1–2 h |

Les deux premières se font tout de suite : **une ligne et un déplacement de fichier**, pour
**7 minutes gagnées par vérification**, soit **40 % de la CI**. Le reste suivra.

## Et pour Jah, concrètement ?

Rien de visible pour toi : ce sont des outils de travail, pas des fonctionnalités. Mais il y a un
effet que tu verras.

Chaque nouvelle idée livrée — une fenêtre qu'on déplace, un réglage de texte, une couleur de
maison — doit être vérifiée avant d'arriver chez toi. Plus cette vérification est lente, moins on
livre de choses, et plus longtemps tu attends. **Réduire la CI de 18 à 7 minutes, c'est livrer
deux fois plus souvent** sans jamais baisser la garde.

C'est aussi une question de vérité : une vérification qui prend un quart d'heure, on est tenté de
la sauter « juste cette fois ». Une vérification qui prend trois minutes, on la lance sans y penser.

---

*Mesures prises le 30 septembre 2026 entre 07 h 30 et 07 h 40, sur les 30 derniers runs GitHub et
sur deux jobs complets (PR #124 et release `alpha.12`). Le rapport détaillé, avec les commandes
utilisées, est dans `docs/plans/ci-tests.md` du dépôt Jah Messenger.*