PrestaFlow en CI hors GitHub Actions : GitLab, Bitbucket, CircleCI, Jenkins
Décor
L’article 2 de la série a posé deux niveaux de CI :
- Niveau 1 — la CLI PrestaFlow dans un workflow, sans compte prestaflow.io, avec Docker pour lancer Flashlight à la main.
- Niveau 2 — l’intégration officielle qui gère Flashlight, le commentaire PR/MR, et la synchro plateforme (report URL, historique, régressions visuelles).
Le niveau 2 n’existe aujourd’hui que pour GitHub Actions. Trois portages sont en préparation, avec les mêmes inputs et le même endpoint côté plateforme, mais aucun n’est encore publié : leurs dépôts GitHub n’ont qu’un tag v0.1.0, et ni le registre GitLab, ni Docker Hub, ni le registre des orbs CircleCI ne les connaissent.
| Plateforme | Niveau 2 | Référence prévue | État |
|---|---|---|---|
| GitHub | Action | PrestaFlow/github-action@v2 | publiée |
| GitLab | CI/CD Component | gitlab.com/prestaflow/ci/prestaflow@v0.1.0 | en préparation |
| Bitbucket | Pipe Docker | docker://prestaflow/pipe-push | en préparation |
| CircleCI | Orb | prestaflow/prestaflow | en préparation |
| Jenkins | — | Jenkinsfile déclaratif | niveau 1 uniquement |
Cette annexe donne donc surtout le niveau 1 pour chaque plateforme, celui qui fonctionne aujourd’hui, et montre la syntaxe prévue du niveau 2 pour que vous sachiez à quoi vous attendre.
Ce qui doit tourner, quel que soit le CI
Le workflow niveau 1 fait six choses, dans l’ordre :
git clonedu projet (implicite dans tout CI)- Installer PHP (typiquement 8.2), Composer et Chromium
composer install- Démarrer une base MariaDB puis Flashlight, en y montant le module
- Installer le module au démarrage de Flashlight (init-script), puis attendre que le back office réponde
- Lancer
composer prestaflow -- run ./tests/prestaflowavec les variables d’environnement qui pointent sur la boutique
Le point 4 conditionne le reste. Pour que PrestaShop voie le module, il faut monter le dépôt cloné dans modules/psflowdemo du conteneur, puis l’installer : c’est l’init-script tests/flashlight-init/10-install-module.sh de l’article 2, qui lance php /var/www/html/bin/console prestashop:module install psflowdemo au premier démarrage.
Or les side-cars déclaratifs (services: de GitLab ou de Bitbucket) ne permettent pas de monter le checkout dans le conteneur. Flashlight sait aussi installer des zips placés dans le dossier INSTALL_MODULES_DIR, mais ce dossier doit exister dans le conteneur, et on n’y a pas accès non plus. La solution qui marche : piloter un démon Docker depuis le job et lancer MariaDB et Flashlight avec docker run, exactement comme dans l’article 2. Chaque plateforme fournit ce démon à sa façon.
Deux réglages valent pour les trois plateformes ci-dessous :
CHROME_NO_SANDBOX=1. Les imagesphp:8.2-clitournent en root, et Chromium refuse de démarrer en root sans--no-sandbox(« Running as root without —no-sandbox is not supported »). PrestaFlow v1.7.1 ne passe pas cette option, mais chrome-php la lit dans cette variable d’environnement et ajoute alors--no-sandbox.- Les variables
PRESTAFLOW_*passent par l’environnement. PrestaFlow v1.7.1 les lit dans$_ENV, rempli seulement sivariables_ordercontientE. L’image officiellephp:8.2-clin’embarque pas dephp.ini: la valeur par défautEGPCSs’applique, donc tout va bien. Sur une image qui charge lephp.inide production (GPCS), ajoutez-d variables_order=EGPCSou unphp.iniadapté.
GitLab CI
Niveau 1 — CLI seule
Sur GitLab, le démon Docker vient d’un service docker:dind. Il faut un runner qui accepte le mode privilégié : c’est le cas des runners Linux partagés de GitLab.com ; sur un runner auto-hébergé, activez privileged = true.
Créez .gitlab-ci.yml à la racine :
stages:
- test
e2e:
stage: test
image: php:8.2-cli
services:
- name: docker:27-dind
alias: docker
parallel:
matrix:
- PS_VERSION: ["8.1.7", "9.0.0"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
PRESTAFLOW_PS_VERSION: $PS_VERSION
PRESTAFLOW_LOCALE: en
PRESTAFLOW_FO_URL: http://docker/
PRESTAFLOW_BO_URL: http://docker/admin-dev/
PRESTAFLOW_BO_EMAIL: admin@prestashop.com
PRESTAFLOW_BO_PASSWD: prestashop
PRESTAFLOW_HEADLESS: "true"
CHROME_NO_SANDBOX: "1"
before_script:
- apt-get update && apt-get install -y --no-install-recommends curl chromium docker-cli 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
- docker network create prestashop
- >-
docker run -d --name mysql --network prestashop
-e MARIADB_ROOT_PASSWORD=prestashop -e MARIADB_DATABASE=prestashop
-e MARIADB_USER=prestashop -e MARIADB_PASSWORD=prestashop
mariadb:11
- >-
docker run -d --name ps --network prestashop -p 80:80
-e PS_DOMAIN=docker -e MYSQL_HOST=mysql
-v "$CI_PROJECT_DIR":/var/www/html/modules/psflowdemo
-v "$CI_PROJECT_DIR/tests/flashlight-init":/tmp/init-scripts:ro
prestashop/prestashop-flashlight:$PS_VERSION
- timeout 300 sh -c 'until [ "$(curl -s -o /dev/null -w "%{http_code}" http://docker/admin-dev/)" = "302" ]; do sleep 2; done' || { docker logs ps; exit 1; }
script:
- composer prestaflow -- run ./tests/prestaflow
artifacts:
when: on_failure
paths:
- prestaflow/screens/errors/
expire_in: 1 week
Quatre particularités GitLab :
- Le service
docker:dindfournit le démon, joignable sous l’aliasdocker. Les conteneurs lancés pardocker runtournent dans ce service : un port publié avec-p 80:80se joint donc surhttp://docker/, et non surlocalhost. D’oùPS_DOMAIN=docker: PrestaShop redirige vers son domaine configuré, qui doit correspondre à l’URL dePRESTAFLOW_FO_URL. - Le montage du checkout fonctionne : le dossier du projet (
$CI_PROJECT_DIR, sous/builds) est partagé entre le job et ses services, ce qui permet au démon dind de le monter dans Flashlight. C’est ce qui rend l’installation du module possible. parallel:matrix:duplique le job pour chaque valeur dePS_VERSION: deux jobs, deux boutiques, deux runs. La variable sert à la fois au tag de l’image Flashlight et àPRESTAFLOW_PS_VERSION.artifacts:ne s’attache qu’on_failure, comme leif: failure()de GitHub. Les captures d’erreur (prestaflow/screens/errors/, prises à l’échec d’une assertion) restent téléchargeables 7 jours.
L’attente sur /admin-dev/ échoue franchement au bout de 5 minutes (timeout renvoie un code non nul, et le || { …; exit 1; } affiche les logs de Flashlight avant d’arrêter le job) : une boucle for qui se contente de break laisserait le job continuer contre une boutique morte. La locale est en : l’image Flashlight n’installe que l’anglais.
Niveau 2 — CI/CD Component (en préparation)
Le portage GitLab de l’Action vit dans le dépôt GitHub PrestaFlow/gitlab-component. Il n’est pas encore publié : le projet gitlab.com/prestaflow/ci n’existe pas, donc la ligne include ci-dessous échoue aujourd’hui. Voici la syntaxe prévue par son README :
include:
- component: gitlab.com/prestaflow/ci/prestaflow@v0.1.0
inputs:
token: $PRESTAFLOW_TOKEN
project_id: pk_01ABCDEF
flashlight: "true"
ps_version: "9.0.0"
flashlight_init_scripts: tests/flashlight-init
mr_comment: "true"
Les inputs du template : token, project_id (Product Key pk_…), execute (lance composer run prestaflow:json:file, le script Composer de l’article 2), suites, flashlight, ps_version, flashlight_mount (auto/root/modules/themes), flashlight_init_scripts, mr_comment, upload_artifacts, visual, ainsi que image (par défaut composer:2) et stage. Le job démarre lui-même un service docker:24-dind pour Flashlight.
Deux variables CI/CD, masquées, à définir côté projet :
PRESTAFLOW_TOKEN— pour l’API.GITLAB_TOKEN— token GitLab avec le scopeapi, pour poster la note sur la MR. LeCI_JOB_TOKENpar défaut ne peut pas commenter les MR.
Le template publie un artifact prestaflow.env (format dotenv) avec PRESTAFLOW_REPORT_ID, PRESTAFLOW_REPORT_URL, PRESTAFLOW_PASSED, PRESTAFLOW_FAILED, PRESTAFLOW_SKIPPED, PRESTAFLOW_TOTAL, PRESTAFLOW_DURATION_MS et PRESTAFLOW_STATUS. Un job en aval y accède via needs: [{job: prestaflow, artifacts: true}].
Bitbucket Pipelines
Niveau 1 — CLI seule
Sur Bitbucket, le démon vient du service docker intégré. Quand il est activé sur un step, la CLI docker est disponible dans le conteneur du build, et les ports publiés par docker run -p se joignent sur localhost, puisque les services partagent le réseau du step.
Créez bitbucket-pipelines.yml :
image: php:8.2-cli
definitions:
services:
docker:
memory: 3072
pipelines:
default:
- step:
name: E2E — PrestaShop 8.1.7
size: 2x
services:
- docker
script:
- apt-get update && apt-get install -y --no-install-recommends curl 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
- docker network create prestashop
- >-
docker run -d --name mysql --network prestashop
-e MARIADB_ROOT_PASSWORD=prestashop -e MARIADB_DATABASE=prestashop
-e MARIADB_USER=prestashop -e MARIADB_PASSWORD=prestashop
mariadb:11
- >-
docker run -d --name ps --network prestashop -p 80:80
-e PS_DOMAIN=localhost -e MYSQL_HOST=mysql
-v "$BITBUCKET_CLONE_DIR":/var/www/html/modules/psflowdemo
-v "$BITBUCKET_CLONE_DIR/tests/flashlight-init":/tmp/init-scripts:ro
prestashop/prestashop-flashlight:8.1.7
- timeout 300 sh -c 'until [ "$(curl -s -o /dev/null -w "%{http_code}" http://localhost/admin-dev/)" = "302" ]; do sleep 2; done' || { docker logs ps; exit 1; }
- export PRESTAFLOW_PS_VERSION=8.1.7
- export PRESTAFLOW_LOCALE=en
- export PRESTAFLOW_FO_URL=http://localhost/
- export PRESTAFLOW_BO_URL=http://localhost/admin-dev/
- export PRESTAFLOW_BO_EMAIL=admin@prestashop.com
- export PRESTAFLOW_BO_PASSWD=prestashop
- export PRESTAFLOW_HEADLESS=true
- export CHROME_NO_SANDBOX=1
- composer prestaflow -- run ./tests/prestaflow
artifacts:
- prestaflow/screens/errors/**
Trois particularités Bitbucket :
- Les montages sont limités au dossier du clone. Bitbucket n’accepte un
-vque sous$BITBUCKET_CLONE_DIR: c’est justement là que se trouvent le module et ses init-scripts. - La mémoire se règle sur le service et sur le step. Le service
dockerdispose par défaut de 1024 Mo, trop peu pour MariaDB et Flashlight réunis. On lui en donne 3072 dansdefinitions, etsize: 2xsur le step double la mémoire totale disponible, pour que Chromium et Composer gardent de la place dans le conteneur du build. - Les artefacts ne sont pas conditionnels : ils sont capturés à la fin du step, qu’il passe ou non. Quand tout passe,
prestaflow/screens/errors/est simplement vide.
Niveau 2 — Pipe (en préparation)
Le pipe vit dans le dépôt GitHub PrestaFlow/bitbucket-pipe. Son image n’est pas encore sur Docker Hub : la référence ci-dessous, tirée de son README, ne se télécharge pas aujourd’hui.
image: php:8.3-cli
definitions:
services:
docker:
memory: 3072
pipelines:
pull-requests:
'**':
- step:
name: PrestaFlow
size: 2x
services: [docker]
artifacts: [prestaflow.env]
script:
- pipe: docker://prestaflow/pipe-push:1.0.0
variables:
TOKEN: $PRESTAFLOW_TOKEN
PROJECT_ID: pk_01ABC...
FLASHLIGHT: 'true'
PS_VERSION: '9.0.0'
FLASHLIGHT_INIT_SCRIPTS: 'tests/flashlight-init'
BITBUCKET_ACCESS_TOKEN: $PRESTAFLOW_BITBUCKET_TOKEN
- step:
name: Notify
script:
- . ./prestaflow.env
- echo "Report → $PRESTAFLOW_REPORT_URL"
Variables prévues : TOKEN, PROJECT_ID, API_URL, EXECUTE, SUITES (sans effet avec PrestaFlow v1.7.1, comme sur GitLab), FLASHLIGHT, PS_VERSION, FLASHLIGHT_MOUNT (auto/root/modules/themes), FLASHLIGHT_INIT_SCRIPTS, PR_COMMENT (automatique sur les builds de pull request), UPLOAD_ARTIFACTS, VISUAL (défaut true) et BITBUCKET_ACCESS_TOKEN, un token d’accès au dépôt avec pullrequest:write pour les commentaires.
Deux points à noter :
services: [docker]est obligatoire dès queFLASHLIGHT=true: le pipe démarre MariaDB et Flashlight avecdocker compose, via le démon Docker du service.- Commentaires PR idempotents : le pipe insère un marqueur
<!-- prestaflow-run:<project-key> -->dans son commentaire, ce qui lui permet de mettre à jour le commentaire existant plutôt que d’en ajouter un à chaque run.
Le prestaflow.env produit contient les mêmes variables que celui du component GitLab (PRESTAFLOW_REPORT_URL, PRESTAFLOW_STATUS, etc.) et se source dans un step suivant.
CircleCI
Niveau 2 — Orb (en préparation)
Le portage CircleCI vit dans le dépôt GitHub PrestaFlow/circleci-orb. L’orb prestaflow/prestaflow n’est pas encore publié dans le registre CircleCI : la configuration ci-dessous, tirée de son README, sera refusée tant qu’il ne l’est pas.
L’orb prévoit un job clé en main, prestaflow/test, et des commandes à composer soi-même : push (qui enchaîne tout), flashlight, install-deps, run-tests, visual-download, visual-upload, upload, comment-pr et set-outputs.
.circleci/config.yml minimal :
version: 2.1
orbs:
prestaflow: prestaflow/prestaflow@1.0.0
workflows:
test:
jobs:
- prestaflow/test:
project_id: pk_01ABCDEFGHIJKLMNOPQR10
flashlight: true
ps_version: "9.0.0"
flashlight_init_scripts: tests/flashlight-init
context: prestaflow
Trois particularités CircleCI :
- Exécuteur
machine: le jobprestaflow/testl’utilise par défaut, car Flashlight doit monter le checkout, ce quesetup_remote_dockerne permet pas. Gardez-le en tête pour votre consommation de crédits. GITHUB_TOKENouBITBUCKET_ACCESS_TOKENà créer à la main : CircleCI n’injecte pas de token VCS. Sans lui, pas de commentaire PR, mais le run remonte quand même sur la plateforme.- Sorties via
BASH_ENV: les variables (PRESTAFLOW_REPORT_URL, etc.) sont exportées dansBASH_ENVet écrites dans un fichierprestaflow.env, plutôt qu’en outputs de step comme sur GitHub.
Niveau 1
Jenkins (déclaratif)
Jenkins n’a pas d’intégration officielle, même en préparation. Le niveau 2 y passe par un curl vers l’API prestaflow.io (voir la fin de l’article) ; le niveau 1 reste la voie normale.
Créez Jenkinsfile à la racine :
pipeline {
agent {
docker {
image 'php:8.2-cli'
args '-u root --network host -v /var/run/docker.sock:/var/run/docker.sock'
}
}
environment {
PRESTAFLOW_PS_VERSION = '8.1.7'
PRESTAFLOW_LOCALE = 'en'
PRESTAFLOW_FO_URL = 'http://localhost/'
PRESTAFLOW_BO_URL = 'http://localhost/admin-dev/'
PRESTAFLOW_BO_EMAIL = 'admin@prestashop.com'
PRESTAFLOW_BO_PASSWD = credentials('prestashop-admin-passwd')
PRESTAFLOW_HEADLESS = 'true'
CHROME_NO_SANDBOX = '1'
}
stages {
stage('Prepare') {
steps {
sh 'apt-get update && apt-get install -y --no-install-recommends curl chromium docker-cli libgd-dev unzip git'
sh 'docker-php-ext-install gd'
sh 'curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer'
sh 'composer install --prefer-dist --no-progress'
}
}
stage('Start Flashlight') {
steps {
sh 'docker run -d --rm --name mysql --network host -e MARIADB_ROOT_PASSWORD=prestashop -e MARIADB_DATABASE=prestashop -e MARIADB_USER=prestashop -e MARIADB_PASSWORD=prestashop mariadb:11'
sh 'docker run -d --rm --name ps --network host -e PS_DOMAIN=localhost -e MYSQL_HOST=127.0.0.1 -v "$WORKSPACE":/var/www/html/modules/psflowdemo -v "$WORKSPACE/tests/flashlight-init":/tmp/init-scripts:ro prestashop/prestashop-flashlight:8.1.7'
sh '''timeout 300 sh -c 'until [ "$(curl -s -o /dev/null -w "%{http_code}" http://localhost/admin-dev/)" = "302" ]; do sleep 2; done' || { docker logs ps; exit 1; }'''
}
}
stage('Run PrestaFlow') {
steps {
sh 'composer prestaflow -- run ./tests/prestaflow'
}
}
}
post {
failure {
archiveArtifacts artifacts: 'prestaflow/screens/errors/**', allowEmptyArchive: true
}
always {
sh 'docker rm -f ps mysql 2>/dev/null || true'
}
}
}
Quatre particularités Jenkins :
- Le socket Docker de l’hôte, pas du Docker-in-Docker. L’agent est un conteneur PHP ; pour qu’il lance Flashlight, on lui monte
/var/run/docker.socket on installe la CLI (docker-cli) dans le stagePrepare. Les conteneurs démarrés tournent alors sur le démon de l’hôte, à côté de l’agent. Donner ce socket revient à donner à l’agent le contrôle du Docker de l’hôte : réservez-le à des nœuds dédiés à la CI. -u root: par défaut, Jenkins lance le conteneur avec l’uid de l’utilisateur de l’agent, qui n’a pas le droit de faireapt-get installni d’accéder au socket. En root,CHROME_NO_SANDBOXdevient nécessaire. Revers : les fichiers créés dans le workspace appartiennent à root, prévoyez uncleanWs()ou un nettoyage équivalent.- Le montage du workspace :
$WORKSPACEest un chemin de l’hôte, que le plugin Docker Pipeline monte au même chemin dans l’agent. Le-v "$WORKSPACE":…fonctionne donc tant que l’agent Jenkins tourne directement sur l’hôte Docker ; si l’agent est lui-même conteneurisé, ce chemin n’existe pas côté démon. credentials()etpost { always }: le Credentials Store fournit le mot de passe sous forme de variable d’environnement, etpost { always }supprime les conteneurs Flashlight et MariaDB même en cas d’échec, pour éviter les zombies sur le nœud.
Ce qui reste identique partout
Peu importe le CI et le niveau, les mêmes règles s’appliquent :
- Une base à côté de Flashlight, et
PS_DOMAIN— l’image embarque nginx et php-fpm mais pas MySQL. Sans base, la boutique réessaie la connexion en boucle et ne répond jamais ; sansPS_DOMAIN, elle s’arrête aussitôt. - Le module monté ET installé — monter le dépôt dans
modules/ne suffit pas : l’init-scriptprestashop:module installs’en charge au premier démarrage. - Attente explicite après le boot Flashlight — sur
/admin-dev/, jusqu’à obtenir un 302 (redirection vers la page de connexion), plutôt qu’un 200 sur/qui prouve seulement que quelque chose écoute sur le port. Et une attente qui échoue franchement au bout du délai. Détails dans l’annexe PrestaShop Flashlight : des boutiques jetables pour tester. - Variables
PRESTAFLOW_*— mêmes noms, mêmes rôles, quelle que soit la plateforme. Le code de la lib ne connaît rien du CI qui l’exécute ; il faut seulement qu’elles arrivent jusqu’à$_ENV(variables_orderavecE). - Artefacts sur échec — les captures d’erreur dans
prestaflow/screens/errors/, prises à l’échec d’une assertion. PrestaFlow ne produit pas de rapport HTML de run ; pour un fichier exploitable par le CI, les options--junit(écritprestaflow/junit.xml) et-o json --file(écritprestaflow/results.json) existent. - Code de sortie non nul de la CLI = job rouge. Ne pas le court-circuiter avec un
|| truebien intentionné. Attention toutefois : en v1.7.1, si une exception interrompt la CLI (Chrome qui ne démarre pas, par exemple), le code de sortie reste 0. Relisez la sortie du job quand un run est vert en quelques secondes.
Au niveau 2, les intégrations remontent les mêmes informations (identifiant et URL du rapport, compteurs, durée, statut), via le mécanisme natif de chaque plateforme. L’Action GitHub les expose en outputs de step, nommés id, report-url, passed, failed, skipped, total, duration-ms et status. Les portages GitLab, Bitbucket et CircleCI les écriront sous la forme PRESTAFLOW_REPORT_ID, PRESTAFLOW_REPORT_URL, PRESTAFLOW_PASSED… dans un prestaflow.env (artifact dotenv, fichier à sourcer ou BASH_ENV).
Le cas Jenkins : niveau 2 fait maison
Sans intégration officielle, deux options si vous êtes sur Jenkins et voulez la synchro plateforme :
- Rester au niveau 1 — vous avez le CI qui rougit sur les régressions fonctionnelles. Vous n’avez ni le commentaire PR, ni l’historique côté plateforme, ni les régressions visuelles synchronisées. C’est déjà utile.
- Faire soi-même l’upload vers l’API prestaflow.io — c’est ce que font l’Action et les portages : un
POST https://api.prestaflow.io/ci/github-action, authentifié par l’en-têteX-Api-Token(votrePRESTAFLOW_TOKEN), en multipart, avec le champprojectId(la Product Key) et leresults.jsondansfile[]. Ce fichier n’existe qu’avec-o json --file, d’où le script Composerprestaflow:json:filede l’article 2. Malgré son nom, l’endpoint accepte n’importe quel CI, mais il classe pour l’instant tous les runs reçus comme venant de GitHub Actions.
Ce second cas justifierait à lui seul un article dédié (idempotence, retries, captures d’écran jointes). Pour l’instant, on note l’option.
Notes
Dans la Série PrestaFlow — article 5 sur 23