Retour au blog
Tests

PrestaFlow : un scénario qui tient de PrestaShop 1.7 à 9

PrestaEdit •
PrestaFlow : un scénario qui tient de PrestaShop 1.7 à 9

Décor

L’article 2 sur la CI a montré la matrice strategy.matrix.ps-version qui lance la même suite contre plusieurs images Flashlight. Encore faut-il que la suite passe sur chacune.

Cette annexe va au bout : comment structurer concrètement ses Pages pour qu’un même scénario passe sur PS 1.7, 8 et 9 sans dupliquer sa logique métier, et comment vérifier ça en local avant de pousser sur la CI.

Ce qui casse typiquement entre versions

Avant d’écrire du code, un inventaire rapide de ce qui varie et fait tomber un scénario d’un passage à l’autre :

  • Layout BO refactoré. Les pages BO ont migré progressivement de Smarty vers Twig au fil des versions majeures. Le même écran a des sélecteurs différents avant/après.
  • Templates front. Le thème Classic évolue entre versions majeures, tunnel de commande compris.
  • URLs friendly. Les slugs par défaut d’un même contenu peuvent changer (ordre, majuscules, présence d’un préfixe langue).
  • Features supprimées. Certaines pages BO disparaissent ou changent d’URL d’une version majeure à l’autre — un scénario qui les visite doit être marqué skip ou repensé sur la version cible.
  • CSRF et sessions. La connexion au BO et ses jetons évoluent (en 9, l’authentification BO passe par le composant de sécurité de Symfony) ; login() gère, mais un code custom qui pilote un formulaire à la main peut se coincer.

Dit autrement : ce qui bouge, ce sont les sélecteurs et parfois le flow — pas l’intention métier. C’est précisément ce que la séparation Page / Suite de l’article 1 est faite pour absorber.

Refactor : psflowdemo en version-portable

Prenons la Page Modules\Psflowdemo\Configuration de psflowdemo, qui pilote la page de configuration du module dans le BO. On veut qu’elle tourne sur v7, v8, v9, avec une seule copie de sa logique.

Le parent commun

Créez tests/prestaflow/Pages/Common/Modules/Psflowdemo/Configuration/Page.php — il porte la logique métier et les sélecteurs, identiques de 1.7 à 9 pour ce module :

<?php

namespace Tests\Pages\Common\Modules\Psflowdemo\Configuration;

use PrestaFlow\Library\Pages\BackOfficePage;

class Page extends BackOfficePage
{
    public function defineSelectors(): array
    {
        return [
            'titleInput' => 'input[name="PSFLOWDEMO_TITLE"]',
            'submitButton' => 'button[name="submitPsflowdemo"]',
            'successAlert' => '.alert.alert-success',
            'invalidTokenContinue' => 'a.btn-continue',
        ];
    }

    public function openConfiguration(string $boUrl): void
    {
        $this->goToUrl(rtrim($boUrl, '/') . '/index.php?controller=AdminModules&configure=psflowdemo');

        // Sans jeton dans l'URL, PrestaShop affiche « Invalid security token »
        // et propose un lien pour continuer.
        if ($this->isVisible($this->getSelector('invalidTokenContinue'), 2000)) {
            $this->click($this->getSelector('invalidTokenContinue'));
            $this->waitForPageReload();
        }
    }

    public function fillTitle(string $title): void
    {
        $this->setValue($this->getSelector('titleInput'), $title);
    }

    public function save(): void
    {
        $this->click($this->getSelector('submitButton'));
        $this->waitForPageReload();
    }

    public function hasSuccessMessage(): bool
    {
        return $this->isVisible($this->getSelector('successAlert'), 5000);
    }
}

Les trois sous-classes de version

Créez ensuite les trois fichiers v7, v8, v9. Chacun étend le parent commun et n’override que ce qui diverge. Pour psflowdemo, rien ne diverge : les trois sont vides.

tests/prestaflow/Pages/v8/Modules/Psflowdemo/Configuration/Page.php :

<?php

namespace Tests\Pages\v8\Modules\Psflowdemo\Configuration;

use Tests\Pages\Common\Modules\Psflowdemo\Configuration\Page as BasePage;

class Page extends BasePage
{
}

tests/prestaflow/Pages/v9/Modules/Psflowdemo/Configuration/Page.php — identique dans notre cas :

<?php

namespace Tests\Pages\v9\Modules\Psflowdemo\Configuration;

use Tests\Pages\Common\Modules\Psflowdemo\Configuration\Page as BasePage;

class Page extends BasePage
{
}

tests/prestaflow/Pages/v7/Modules/Psflowdemo/Configuration/Page.php — toujours identique :

<?php

namespace Tests\Pages\v7\Modules\Psflowdemo\Configuration;

use Tests\Pages\Common\Modules\Psflowdemo\Configuration\Page as BasePage;

class Page extends BasePage
{
}

Le jour où un sélecteur diverge sur une version, c’est dans sa sous-classe qu’on le surcharge. Par exemple, si le bouton portait un autre nom en 1.7 (cas hypothétique, ce n’est pas le cas de psflowdemo) :

public function defineSelectors(): array
{
    return array_merge(parent::defineSelectors(), [
        'submitButton' => 'button[name="submitpsflowdemo"]',
    ]);
}

Trois fichiers vides — c’est le coût d’entrée pour la portabilité. Toute la logique reste dans le parent, un seul endroit à modifier quand le comportement métier évolue.

La Page Home de psflowdemo suit le même principe, avec une variante : la classe v8 porte la logique (sélecteurs du bloc, getBlockTitle()), et les classes v7 et v9 l’étendent sans rien ajouter.

La mécanique de routage

Quand une suite fait :

$this->importPage('Modules\Psflowdemo\Configuration', domain: 'Tests');

La lib construit le nom de classe ainsi (trait ImportPage, v1.7.1) :

$pageClass = $domain.'\\Pages\\v'.$this->getMajorVersion(namespace: true).'\\'.$pageName.'\\Page';

getMajorVersion() part de PRESTAFLOW_PS_VERSION (8.1.7 → 8, 1.7.8.11 → 7 pour le namespace), le namespace est construit, la classe est instanciée. Il n’y a pas de tentative avec une autre version si le fichier n’existe pas — c’est pour ça que chaque v7/, v8/, v9/ doit exister.

Le paramètre domain change le préfixe du namespace : par défaut il pointe sur \PrestaFlow\Library (les Pages fournies par la lib), passer 'Tests' route vers votre propre namespace Tests\Pages\....

Exécuter la même suite contre les trois versions, en local

La matrice CI de l’article 2 fait tourner les trois versions en parallèle sur GitHub Actions. Pratique pour la vérification finale, mais lent pour la boucle courte du développement.

Un petit script shell fait le même travail localement, en série, avec Flashlight :

#!/usr/bin/env bash
set -e

if [ -f .env.local ]; then
    echo ".env.local existe déjà : renommez-le avant de lancer ce script." >&2
    exit 1
fi
trap 'rm -f .env.local' EXIT

docker network create prestaflow 2>/dev/null || true

for version in 1.7.8.11 8.1.7 9.0.0; do
    echo "=== PrestaShop $version ==="

    # Une base neuve par version : Flashlight n'embarque pas MySQL.
    docker rm -f ps mysql 2>/dev/null || true
    docker run -d --name mysql --network prestaflow \
        -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 prestaflow -p 80:80 \
        -e PS_DOMAIN=localhost \
        -e MYSQL_HOST=mysql \
        -v "$PWD":/var/www/html/modules/psflowdemo \
        -v "$PWD/tests/flashlight-init":/tmp/init-scripts:ro \
        prestashop/prestashop-flashlight:$version

    # Attente du boot : le back office redirige (302) vers la page de
    # connexion une fois la boutique vraiment servie.
    for i in $(seq 1 60); do
        code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost/admin-dev/ || true)
        [ "$code" = "302" ] && break
        sleep 2
    done
    [ "$code" = "302" ] || { echo "PrestaShop $version n'a pas démarré"; docker logs ps; exit 1; }

    # La configuration du tour passe par .env.local, que la lib lit à la
    # place de .env (voir plus bas).
    {
        echo "PRESTAFLOW_PS_VERSION=$version"
        echo "PRESTAFLOW_LOCALE=en"
        echo "PRESTAFLOW_FO_URL=http://localhost/"
        echo "PRESTAFLOW_BO_URL=http://localhost/admin-dev/"
        echo "PRESTAFLOW_BO_EMAIL=admin@prestashop.com"
        echo "PRESTAFLOW_BO_PASSWD=prestashop"
    } > .env.local
    composer prestaflow -- run ./tests/prestaflow
done

docker rm -f ps mysql

Le module est monté dans modules/psflowdemo et installé au boot par l’init-script de l’article sur la CI (tests/flashlight-init/10-install-module.sh). Trois autres détails comptent. Flashlight n’embarque pas MySQL : chaque version a sa propre base MariaDB, sur un réseau Docker partagé, et la boutique réessaie de s’y connecter jusqu’à ce qu’elle réponde. PS_DOMAIN est obligatoire (sans lui, le conteneur s’arrête aussitôt) et doit correspondre à l’URL vue depuis votre machine. Enfin, vérifiez les tags avant de les épingler : 1.7.8 n’existe pas, 1.7.8.11 oui, et les versions récentes n’existent qu’avec un suffixe (9.2.0-nginx). L’annexe PrestaShop Flashlight : des boutiques jetables pour tester détaille ces points.

Côté PrestaFlow, le seul truc qui change entre chaque itération : la variable PRESTAFLOW_PS_VERSION qui pilote le routage importPage. Les scénarios ne connaissent pas la version qu’ils testent, ce sont les Pages qui s’adaptent.

Pourquoi un .env.local plutôt qu’un simple PRESTAFLOW_PS_VERSION=$version composer prestaflow -- … ? La bibliothèque v1.7.1 lit sa configuration dans $_ENV, que PHP laisse vide quand variables_order ne contient pas le E (le réglage des php.ini fournis avec PHP). La variable passée en ligne de commande est alors ignorée sans message, et le .env aussi, puisque la variable existe déjà : toutes les itérations tourneraient avec la version par défaut de la lib. .env.local, quand il existe, est lu à la place de .env : le script en écrit un à chaque tour, avec les valeurs d’une boutique Flashlight (anglais, admin@prestashop.com / prestashop), et le supprime à la fin. Le ./ devant tests/prestaflow compte aussi : la v1.7.1 met la première lettre du chemin en majuscule, ce qui passe sur macOS mais pas sous Linux.

Dernier point, sur les suites qui passent commande, comme Checkout dans psflowdemo. Sur une boutique Flashlight neuve, les modules de paiement sont réservés au Royaume-Uni, et la France n’est même pas active en 1.7.8.11 : avec l’adresse française du scénario, aucun moyen de paiement n’est proposé. Pour y remédier, l’init-script tests/flashlight-init/30-enable-payment.sh du module psflowdemo active la France et ouvre les modules de paiement à tous les pays actifs. Avec ce script, Checkout passe de bout en bout sur les trois versions (vérifié sur des boutiques neuves en 1.7.8.11, 8.1.7 et 9.0.0).

Cas rare : conditionnelle par version dans une Page

Il arrive qu’un comportement UI diverge sans qu’on veuille dupliquer toute la Page — par exemple, un modal de confirmation qui n’existe qu’à partir de PS 9.

Deux approches, dans l’ordre à privilégier :

1. Extraire dans une sous-méthode overridable. La méthode publique reste identique, la logique divergente est isolée dans une méthode protégée que la version concernée override.

// Dans Common
public function save(): void
{
    $this->click($this->getSelector('submitButton'));
    $this->confirmIfNeeded();
    $this->waitForPageReload();
}

protected function confirmIfNeeded(): void
{
    // Comportement par défaut : ne rien faire.
}
// Dans v9 uniquement
protected function confirmIfNeeded(): void
{
    $this->click('.modal-footer button.confirm');
}

2. Tester la version dans le corps de méthode. Pragmatique mais dilue la promesse “un dossier par version”, à réserver aux cas très locaux ou temporaires.

public function save(): void
{
    $this->click($this->getSelector('submitButton'));
    if ($this->getMajorVersion() >= 9) {
        $this->click('.modal-footer button.confirm');
    }
    $this->waitForPageReload();
}

Reco : privilégier l’override, ne recourir à getMajorVersion() que quand la divergence est vraiment marginale et qu’un fichier v9 dédié serait de la sur-ingénierie.

Notes

Dans la Série PrestaFlow — article 6 sur 23