Retour au blog
Tests

PrestaFlow : cacher le binaire Chrome en CI

PrestaEdit •
PrestaFlow : cacher le binaire Chrome en CI

Décor

Sous le capot, PrestaFlow parle à Chrome via chrome-php/chrome, un client CDP en PHP pur. Contrairement à Playwright ou Puppeteer, chrome-php n’embarque pas de binaire : il lance celui qu’on lui désigne. Son ordre de recherche, sous Linux :

  1. la variable d’environnement CHROME_PATH, si elle est définie ;
  2. sinon, le premier binaire trouvé sur le PATH parmi google-chrome, chromium-browser, chrome et chromium ;
  3. sinon, chrome, qui a toutes les chances de ne pas exister.

C’est donc au workflow CI de garantir qu’un Chrome ou un Chromium est disponible avant le premier scénario, et de le désigner par CHROME_PATH quand il n’est pas celui que le PATH trouverait.

Cas où c’est gratuit :

  • runs-on: ubuntu-latest : l’image des runners GitHub embarque Google Chrome (google-chrome). Rien à faire.

Cas où c’est douloureux :

  • Conteneur CI (container: php:8.2-cli sur GitHub, exécuteur Docker GitLab, Bitbucket Pipelines) : l’image PHP officielle n’a pas Chrome. Sans installation, chaque run échoue au démarrage du navigateur avec « Chrome process stopped before startup completed. Additional info: sh: 1: exec: chrome: not found ».
  • Runner auto-hébergé minimal : souvent une VM Debian slim où Chrome n’a jamais été installé.

Dans ces cas, l’installation (téléchargement, dépendances système, extraction) se paie à chaque job, souvent plusieurs dizaines de secondes. Faites le calcul pour votre projet : à 45 s par job et 40 jobs par jour, c’est une demi-heure de runner perdue chaque jour, et autant d’attente sur les PR.

Trois patterns d’installation

1. apt-get install : simple, non épinglé

Le plus court, dans un conteneur Debian comme php:8.2-cli, quand la version exacte de Chrome ne pèse sur rien (pas de tests visuels) :

jobs:
  e2e:
    runs-on: ubuntu-latest
    container: php:8.2-cli
    env:
      CHROME_NO_SANDBOX: '1'
    steps:
      - name: Install Chromium
        run: |
          apt-get update
          apt-get install -y --no-install-recommends chromium

Le paquet Debian installe /usr/bin/chromium, que chrome-php trouve seul sur le PATH. Le conteneur tourne en root, et Chromium refuse de démarrer en root sans --no-sandbox (« Running as root without —no-sandbox is not supported ») : CHROME_NO_SANDBOX=1 demande à chrome-php d’ajouter ce flag.

La version est celle de la distribution : elle bougera à chaque mise à jour de l’image de base.

2. browser-actions/setup-chrome : épinglé, mais pas mis en cache

L’action communautaire browser-actions/setup-chrome télécharge un canal (stable, beta, dev, canary) ou une version exacte de Chrome for Testing, et expose le chemin du binaire dans son output chrome-path.

- name: Setup Chrome
  id: setup-chrome
  uses: browser-actions/setup-chrome@v2
  with:
    chrome-version: '129.0.6668.100'
    install-dependencies: true

Deux points à connaître :

  • Le binaire installé n’est pas celui que chrome-php choisirait. Sur ubuntu-latest, google-chrome est déjà sur le PATH, et c’est lui que chrome-php lancerait. Pour utiliser la version épinglée, passez CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} dans l’environnement de l’étape qui lance PrestaFlow.
  • Rien n’est conservé d’un job à l’autre. L’action range Chrome dans le tool cache du runner (RUNNER_TOOL_CACHE), qu’elle consulte avant de télécharger. Mais sur un runner hébergé par GitHub, ce dossier repart vide à chaque job : le téléchargement se paie à chaque fois. Pour le conserver, ajoutez un actions/cache sur ce dossier (voir la recette plus bas).

3. Chrome for Testing dans un cache manuel

Utile hors de GitHub Actions ou pour maîtriser entièrement le chemin et la version. Chrome for Testing publie une archive par version exacte, ce qui rend l’épinglage réel : la clé de cache et l’URL désignent la même version.

- name: Cache Chrome
  id: cache-chrome
  uses: actions/cache@v4
  with:
    path: ~/.cache/chrome-for-testing
    key: chrome-${{ runner.os }}-129.0.6668.100

- name: Download Chrome if not cached
  if: steps.cache-chrome.outputs.cache-hit != 'true'
  run: |
    mkdir -p ~/.cache/chrome-for-testing
    curl -sSLo /tmp/chrome.zip \
      https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.100/linux64/chrome-linux64.zip
    unzip -q /tmp/chrome.zip -d ~/.cache/chrome-for-testing

- name: Point chrome-php to the cached Chrome
  run: echo "CHROME_PATH=$HOME/.cache/chrome-for-testing/chrome-linux64/chrome" >> "$GITHUB_ENV"

Le cache ne contient que Chrome, pas les bibliothèques système dont il dépend (libnss3, libgbm1…). Sur ubuntu-latest, elles sont déjà là, puisque Google Chrome est préinstallé. Sur une image minimale, installez-les à chaque job, en dehors du if : après un cache hit, Chrome serait présent mais ne démarrerait pas.

Verbeux, mais la version ne dépend plus de l’image du runner.

La recette recommandée (GitHub Actions + Flashlight)

Sur un projet à tests visuels, la version de Chrome fait partie de la définition du build : une montée de version silencieuse casse les baselines. On épingle la version dans le workflow, on la désigne à chrome-php par CHROME_PATH, et on la fait monter volontairement (comme composer.lock ou package-lock.json).

name: PrestaFlow

on:
  pull_request:
  push:
    branches: [main]

env:
  CHROME_VERSION: '129.0.6668.100'

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

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: gd
          tools: composer:v2
          ini-values: variables_order=EGPCS

      - name: Restore Chrome from cache
        uses: actions/cache@v4
        with:
          path: ${{ runner.tool_cache }}/setup-chrome
          key: setup-chrome-${{ runner.os }}-${{ env.CHROME_VERSION }}

      - name: Setup Chrome (pinned)
        id: setup-chrome
        uses: browser-actions/setup-chrome@v2
        with:
          chrome-version: ${{ env.CHROME_VERSION }}
          install-dependencies: true

      - name: Install PHP dependencies
        run: composer install --prefer-dist --no-progress

      - uses: PrestaFlow/github-action@v2
        env:
          CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
        with:
          token: ${{ secrets.PRESTAFLOW_TOKEN }}
          projectId: 'pk_01ABC...'
          flashlight: true
          ps-version: '9.0.0'
          flashlight-init-scripts: tests/flashlight-init

actions/cache restaure le tool cache de setup-chrome avant que l’action ne s’exécute : elle y trouve la version demandée et ne télécharge plus rien. Au premier run après une montée de version, la clé change, le téléchargement se paie une fois, et le cache est sauvegardé en fin de job.

CHROME_PATH est lu par chrome-php dans l’environnement du processus, quelle que soit la configuration PHP. Les variables PRESTAFLOW_*, elles, sont lues par PrestaFlow v1.7.1 dans $_ENV, que le php.ini de production installé par setup-php ne remplit pas (variables_order=GPCS) : ini-values: variables_order=EGPCS rétablit ce comportement, pour le jour où vous passez des identifiants BO par env:.

Mesurer le gain

GitHub Actions affiche la durée de chaque étape dans le détail du run : comparez l’étape d’installation de Chrome sur un premier run (cache vide) et sur le suivant. Ce qui se paie, selon la configuration :

ConfigurationPremier jobJobs suivants
ubuntu-latest, Chrome préinstallérienrien
container: php:8.2-cli, apt-get install chromiuminstallation aptinstallation apt (pas de cache)
ubuntu-latest, setup-chrome seultéléchargementtéléchargement (pas de cache entre jobs)
ubuntu-latest, setup-chrome + actions/cachetéléchargementrestauration du cache
ubuntu-latest, cache manuel Chrome for Testingtéléchargementrestauration du cache

Les ordres de grandeur dépendent du runner, du miroir et de la taille de l’archive (environ 160 Mo pour Chrome for Testing 129 sous Linux) : mesurez chez vous avant d’optimiser.

GitLab CI

L’image composer:2 est basée sur Alpine : pas d’apt-get, et ses paquets n’ont rien à voir avec ceux de Debian. On part donc de php:8.2-cli (Debian), comme dans l’annexe CI hors GitHub Actions, et on met en cache les paquets .deb téléchargés par apt :

test:
  image: php:8.2-cli
  variables:
    CHROME_NO_SANDBOX: "1"
  cache:
    key: apt-chromium
    paths:
      - .cache/apt
  before_script:
    - mkdir -p .cache/apt/partial
    - apt-get update
    - apt-get install -y --no-install-recommends -o dir::cache::archives="$CI_PROJECT_DIR/.cache/apt" chromium libgd-dev unzip git
    - docker-php-ext-install gd
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - composer install --prefer-dist --no-progress
  script:
    - composer prestaflow -- run ./tests/prestaflow

GitLab n’accepte en cache que des chemins situés dans le dossier du projet, d’où .cache/apt et l’option dir::cache::archives qui y redirige apt. Ce cache évite les téléchargements, pas l’installation elle-même : copier seulement /usr/bin/chromium ne suffirait pas, car sous Debian ce n’est qu’un script de lancement, qui s’appuie sur /usr/lib/chromium/ et sur des dizaines de bibliothèques.

Pour supprimer tout le coût, la vraie solution sur GitLab est une image : un Dockerfile FROM php:8.2-cli qui installe Chromium, GD et Composer, poussé dans le registre de conteneurs du projet et référencé dans image:. Chromium est alors installé une fois, à la construction de l’image, et sa version ne bouge que quand vous reconstruisez.

Le nom du binaire varie selon la base : chromium sous Debian et Alpine, chromium-browser sous Ubuntu (où le paquet apt passe désormais par snap). Vérifiez avec command -v chromium dans un premier run, ou fixez CHROME_PATH.

Le piège des baselines visuelles

Chrome rend le texte, les ombres CSS et l’anticrénelage légèrement différemment d’une version majeure à l’autre. Une baseline capturée avec la 128 et comparée avec la 129 peut tomber sous le seuil de similitude et échouer, sans qu’aucune ligne de code ait bougé.

Trois disciplines qui verrouillent ça :

  • Épinglez la version de Chrome dans le workflow (numéro exact, pas stable), et vérifiez par CHROME_PATH que c’est bien elle que chrome-php lance.
  • Documentez la version de Chrome dans le dépôt : un .chrome-version à côté du composer.json, ou un commentaire au-dessus de la ligne CHROME_VERSION du workflow.
  • Faites monter Chrome comme composer.lock : PR dédiée, baselines régénérées si besoin (cf. versionner ses baselines visuelles).

Interaction avec les autres annexes

  • Automatiser ses scénarios PrestaFlow en CI : l’article de base ne parle pas du binaire Chrome parce que ubuntu-latest le fournit. Cette annexe couvre les cas où ce n’est plus vrai.
  • Régressions visuelles et versionner ses baselines visuelles : l’épinglage de Chrome est un préalable pour des baselines qui ne dérivent pas.
  • CI hors GitHub Actions : GitLab, Bitbucket et Jenkins n’ont pas les images ubuntu-latest de GitHub, l’installation de Chrome y est presque toujours nécessaire.
  • Runner auto-hébergé : sur un runner auto-hébergé, Chrome n’est plus une affaire de workflow mais de provisionnement de la machine. Un Chrome installé une fois pour toutes vaut mieux qu’un cache téléchargé à chaque job.

Notes

Dans la Série PrestaFlow — article 34 sur 36