Skip to content

CI/CD avec GitHub Actions et GitLab CI : tutoriel Node.js

Keltoum Malouki9 min de lecture
CI/CD avec GitHub Actions et GitLab CI : tutoriel Node.js

Comprendre le CI/CD en pratiquant : un projet Node.js avec Jest, deux configurations pour GitHub Actions et GitLab CI, et les erreurs à éviter pour réussir son premier pipeline.

Dans ce tutoriel, je présente les bases du CI/CD avec GitHub Actions et GitLab CI, puis deux configurations pour automatiser les tests d’un petit projet Node.js avec Jest.

J’ai créé le dépôt CI-GitHub-GitLab comme support de live coding et de veille technique. Cet article prolonge ma présentation sur le CI/CD avec un exemple que vous pouvez explorer, modifier et utiliser pour apprendre.

L’objectif est concret : à chaque changement concerné, lancer les tests automatiquement et obtenir un résultat visible avant de fusionner le code.

CI, livraison continue et déploiement continu : quelles différences ?

Ces trois notions sont liées, mais elles ne désignent pas exactement le même travail.

NotionObjectifExemple
Intégration continue, ou CIIntégrer fréquemment les changements et les vérifier automatiquementExécuter les tests lors d’une pull request
Livraison continue, ou Continuous DeliveryGarder une version prête à être déployéePréparer une version validée dont la mise en production peut nécessiter une décision humaine
Déploiement continu, ou Continuous DeploymentDéployer automatiquement les changements qui passent les validations prévuesMettre une version en production après la réussite du pipeline

Un pipeline est l’ensemble des tâches automatisées qui réalisent ces vérifications ou ces livraisons. Il peut commencer par un seul job de test, puis évoluer selon les besoins du projet.

Un pipeline qui lance des tests ne déploie pas automatiquement une application. Le projet présenté ici est avant tout une démonstration d’intégration continue. Les notions de livraison et de déploiement permettent de comprendre la suite possible.

Le CI/CD peut accompagner une organisation Agile. Il ne remplace ni la méthode de travail de l’équipe, ni la revue de code.

Pourquoi automatiser les tests ?

Une modification peut fonctionner sur un poste de développement et casser un comportement déjà présent. Sans vérification automatique, le problème peut être découvert après la fusion, ou par un utilisateur.

La CI permet de répéter les mêmes contrôles dans un environnement défini. Elle rend aussi leur résultat visible pour les personnes qui relisent le changement.

Sa valeur dépend cependant des contrôles configurés. Un pipeline vert signifie que ces contrôles ont réussi ; il ne prouve pas que toute l’application est correcte.

Pour commencer, un test simple suffit à comprendre ce mécanisme.

Ce que contient mon projet CI-GitHub-GitLab

Voici les principaux fichiers du dépôt :

FichierRôle
sum.jsFonction JavaScript qui additionne deux nombres
sum.test.jsTest Jest qui vérifie que 1 + 2 donne 3
jest.config.cjsConfiguration de Jest
package.json et package-lock.jsonScripts du projet et dépendances
.github/workflows/ci.ymlWorkflow exécuté par GitHub Actions
.gitlab-ci.ymlConfiguration destinée à GitLab CI/CD

Le test présent dans le dépôt est volontairement court :

const sum = require('./sum');

test('adds 1 + 2 = 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Ce test rend un échec facile à provoquer et à comprendre. Il sert à observer le fonctionnement de la CI, pas à représenter une suite de tests complète.

Dans la version du dépôt utilisée pour cet article, le workflow GitHub lance l’installation, les tests, une commande de build et l’archivage du dossier dist. Le pipeline GitLab contient uniquement un job de test.

Il faut préciser une limite de la démonstration : le script build exécute echo build step. Il ne compile pas une application. Le dossier dist est déjà présent dans le dépôt ; son archivage ne prouve donc pas qu’un build l’a généré. Aucun déploiement n’est configuré.

Préparer le projet en local

Pour suivre l’exemple, il faut Git, Node.js, npm et un dépôt sur la plateforme choisie. Les configurations proposées ci-dessous utilisent Node.js 24, une branche LTS maintenue à la date de préparation de l’article.

Vous pouvez récupérer le projet ainsi :

git clone https://github.com/Keltoummalouki/CI-GitHub-GitLab.git
cd CI-GitHub-GitLab
npm ci
npm test

Pour pousser vos propres changements, faites d’abord un fork sur GitHub ou importez le projet dans votre compte GitLab. Le clonage du dépôt d’origine ne donne pas de droits d’écriture sur celui-ci.

La commande npm ci installe les dépendances à partir du fichier de verrouillage. Elle nécessite un package-lock.json cohérent avec package.json et échoue si les deux ne correspondent pas. C’est utile en CI pour éviter de modifier les versions verrouillées pendant l’installation.

Les deux configurations suivantes sont des propositions pour ce tutoriel, et non une copie exacte des fichiers du dépôt. Elles recentrent l’exemple sur les tests, utilisent npm ci et remplacent Node.js 18, présent dans le workflow initial, par Node.js 24. Validez-les dans votre fork avant de les adopter.

Configurer GitHub Actions pour les tests Node.js

GitHub Actions lit les workflows placés dans .github/workflows/.

Dans votre fork, le fichier .github/workflows/ci.yml peut contenir :

name: Node.js CI

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

permissions:
  contents: read

jobs:
  tests:
    name: Jest tests
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Retrieve repository files
        uses: actions/checkout@v7
        with:
          persist-credentials: false

      - name: Prepare Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '24'
          cache: npm

      - name: Install locked dependencies
        run: npm ci

      - name: Run Jest
        run: npm test -- --ci

Le workflow se déclenche lors d’un push sur main et pour les événements habituels d’une pull request ciblant main, notamment son ouverture et l’ajout de nouveaux commits. Si votre branche principale porte un autre nom, adaptez les deux filtres.

Le job utilise une machine fournie par GitHub. Ses étapes récupèrent le code, préparent Node.js, installent les dépendances, puis lancent Jest.

Le cache npm permet de réutiliser des téléchargements de paquets. Il ne remplace pas npm ci.

Après avoir poussé le fichier, ouvrez l’onglet Actions du dépôt. En cas d’échec, sélectionnez l’exécution puis l’étape concernée pour lire les logs.

Les versions majeures des actions rendent l’exemple lisible. Dans un projet qui exige un verrouillage strict des dépendances CI, vous pouvez les remplacer par des SHA de commits vérifiés et organiser leurs mises à jour.

Configurer le même contrôle avec GitLab CI/CD

Sur GitLab, le point d’entrée habituel est le fichier .gitlab-ci.yml à la racine.

Voici une proposition qui lance les tests pour les merge requests et pour les pushs sur la branche par défaut :

default:
  image: node:24-bookworm-slim

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

stages:
  - test

jest_tests:
  stage: test
  timeout: 10m
  script:
    - npm ci
    - npm test -- --ci

L’image fournit l’environnement Node.js. Cette configuration suppose un runner compatible avec les images de conteneurs, par exemple un runner utilisant l’exécuteur Docker. Avec un exécuteur Shell, il faut préparer Node.js sur la machine du runner.

Le job appartient au stage test. Les commandes de son script sont exécutées dans l’ordre. Les règles limitent les déclenchements ; les tags et les exécutions planifiées ne sont pas inclus dans cet exemple.

Après l’import du projet, vérifiez qu’un runner adapté est disponible, puis consultez le pipeline et les logs du job dans GitLab.

Déposer un fichier .gitlab-ci.yml sur GitHub ne suffit pas à lancer GitLab CI/CD. Le projet doit aussi être présent et configuré sur GitLab.

Vérifier que la CI détecte réellement une erreur

La meilleure démonstration consiste à faire échouer volontairement le test dans une branche d’exercice.

  1. Créez une branche dans votre fork ou votre copie du projet.
  2. Remplacez temporairement toBe(3) par toBe(4) dans le test.
  3. Poussez la branche et ouvrez une pull request ou une merge request vers la branche principale.
  4. Vérifiez que le job de test échoue et que les logs montrent la différence entre le résultat attendu et le résultat reçu.
  5. Rétablissez toBe(3), poussez la correction et vérifiez la nouvelle exécution.

Le résultat attendu est un pipeline rouge avec l’attente incorrecte, puis vert après la correction.

Une vérification automatique ne bloque pas nécessairement une fusion à elle seule. Pour en faire une condition de fusion, configurez les contrôles obligatoires ou les règles correspondantes sur votre plateforme.

GitHub Actions et GitLab CI : les repères à retenir

BesoinGitHub ActionsGitLab CI/CD
Définir l’automatisationWorkflow YAML dans .github/workflows/Configuration .gitlab-ci.yml
Définir les déclencheursÉvénements sous onRègles du workflow et des jobs
Organiser les tâchesJobs contenant des stepsJobs associés à des stages
Définir des dépendancesneeds entre jobsStages et, si nécessaire, needs
Exécuter les tâchesRunnerRunner
Conserver des résultatsArtifactsArtifacts
Réutiliser des fichiers pour accélérer les exécutionsCacheCache

Les deux outils permettent de construire une CI utile. Pour ce premier exercice, le choix peut simplement suivre l’hébergement de votre projet.

Cache et artifacts : quelle différence ?

Un cache sert surtout à accélérer une nouvelle exécution, par exemple en réutilisant des paquets téléchargés.

Un artifact conserve un résultat à consulter ou à transmettre : rapport de tests, archive ou fichiers produits par un build.

Dans ce projet, l’archivage de dist illustre le mécanisme des artifacts. Pour aller plus loin, il faudrait une vraie commande de build qui produit ce dossier à partir des sources.

Les erreurs fréquentes quand on débute

Le workflow ne démarre pas. Vérifiez le chemin du fichier, l’indentation YAML, la branche ciblée et les règles de déclenchement.

Les tests fonctionnent en local, mais pas en CI. Comparez la version de Node.js, les dépendances verrouillées, les variables nécessaires et les chemins de fichiers. La sensibilité à la casse peut aussi différer entre votre ordinateur et le runner.

Le job GitLab reste en attente. Vérifiez la disponibilité du runner et, si vous en utilisez, les tags demandés par le job.

Le pipeline est vert, mais l’application n’a pas changé. Vérifiez qu’une vraie étape de déploiement existe. Un test réussi ou un artifact créé ne réalise pas une mise en production.

Le build réussit sans produire de fichiers. Lisez le script lancé par npm run build. Dans cette démonstration, il affiche seulement un message.

Faire évoluer ce premier pipeline

Une fois les tests compris et vérifiés, ajoutez progressivement les contrôles utiles à votre application : lint, vérification TypeScript, tests d’intégration ou véritable build.

Vous pourrez ensuite préparer un déploiement vers un environnement de test, puis définir les conditions de mise en production. Gardez les secrets dans les mécanismes prévus par la plateforme et limitez les permissions aux besoins des jobs.

Le prochain exercice que je propose est simple : ajouter un deuxième test, provoquer son échec, puis suivre sa correction dans le pipeline. Cela permet de vérifier que l’automatisation apporte un retour exploitable à chaque changement.

Code source et documentation

Keltoum Malouki — développeuse Full Stack. Retrouvez mes projets et mon parcours sur mon portfolio.