PrestaFlow : tester un module tiers
Décor
Jusqu’ici, la série a supposé que vous êtes l’auteur du module que vous testez. Les scénarios vivaient dans tests/prestaflow/ à la racine du module, la Page connaissait les sélecteurs parce que vous les aviez choisis.
Il y a un autre cas — potentiellement plus fréquent en pratique : vous êtes intégrateur, agence ou marchand, et vous installez un module que quelqu’un d’autre a écrit sur votre boutique. Vous voulez vous assurer qu’il fonctionne sur votre thème, votre version PS, votre config. Vous ne pouvez ni le modifier (les upgrades l’écraseraient), ni changer ses sélecteurs.
C’est ce cas qu’on couvre ici, avec un module fil rouge que tout le monde a sous la main : ps_emailsubscription, le bloc newsletter livré nativement avec PrestaShop.
Où vivent les tests d’un module tiers ?
Trois emplacements possibles, dont un seul est raisonnable :
- Dans le module — non. La prochaine mise à jour du module (ZIP ou Composer) écrase vos tests.
- Dans un module PS séparé qu’on écrit pour ça — trop de cérémonie pour un dossier de tests.
- Dans un repo dédié, à côté de votre projet PrestaShop.
C’est la troisième option qu’on retient. Concrètement, un repo qa-boutique (ou n’importe quel nom parlant) avec une structure minimale :
qa-boutique/
├─ composer.json ← require-dev prestaflow/php-library
├─ .env ← URLs et credentials de votre boutique de recette
└─ tests/
└─ prestaflow/
├─ Pages/v8/Modules/
└─ Suites/
Le composer.json :
{
"require-dev": {
"prestaflow/php-library": "^1.0"
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/prestaflow/"
}
},
"config": {
"vendor-dir": "vendor-dev",
"process-timeout": 0
},
"scripts": {
"prestaflow": "./vendor-dev/prestaflow/php-library/bin/prestaflow"
}
}
Rien de neuf par rapport à l’article 1 (même vendor-dev/, même script prestaflow, donc même commande composer prestaflow -- run ./tests/prestaflow) — on déplace juste le point d’ancrage : le repo qui héberge PrestaFlow n’est plus le module testé, c’est votre projet de QA.
Une Page pour un module qu’on ne contrôle pas
Créez tests/prestaflow/Pages/v8/Modules/PsEmailsubscription/NewsletterBlock/Page.php :
<?php
namespace Tests\Pages\v8\Modules\PsEmailsubscription\NewsletterBlock;
use PrestaFlow\Library\Pages\v8\FrontOffice\Page as BasePage;
class Page extends BasePage
{
public function defineSelectors(): array
{
// Balisage du thème Classic. Un thème custom peut surcharger le template
// du module : à valider sur votre thème avant de vous fier à ces sélecteurs.
return [
'block' => '.block_newsletter',
'emailInput' => '.block_newsletter input[name="email"]',
'actionInput' => '.block_newsletter input[name="action"]',
'submitButton' => '.block_newsletter input[name="submitNewsletter"]',
'message' => '.block_newsletter .alert-success, .block_newsletter .alert-danger',
'successMessage' => '.block_newsletter .alert-success',
];
}
public function isBlockVisible(): bool
{
return $this->isVisible($this->getSelector('block'), 5000);
}
public function subscribe(string $email): void
{
$this->submitNewsletterForm($email, 0);
}
public function unsubscribe(string $email): void
{
$this->submitNewsletterForm($email, 1);
}
public function getResultMessage(): string
{
return trim((string) $this->getTextContent($this->getSelector('message')));
}
public function hasSucceeded(): bool
{
return $this->isVisible($this->getSelector('successMessage'));
}
private function submitNewsletterForm(string $email, int $action): void
{
$this->setValue($this->getSelector('emailInput'), $email);
// Champ caché du module : 0 = inscription, 1 = désinscription.
$this->setValueByJs($this->getSelector('actionInput'), (string) $action);
$this->click($this->getSelector('submitButton'));
// Le module répond en AJAX, sans recharger la page : on attend le message.
$this->waitVisible($this->getSelector('message'), 10000);
}
}
Deux règles de survie s’appliquent quand vous testez un module que vous ne contrôlez pas :
Sélecteurs les plus stables possibles. L’auteur peut refactorer son template sans prévenir. Un id, un attribut name ou une classe manifestement conçus par lui (.block_newsletter, input[name="submitNewsletter"]) survivront mieux qu’une position DOM (div > form > input:nth-child(2)). Le sélecteur message ci-dessus liste explicitement les deux variantes possibles (alert-success, alert-danger) plutôt que de parier sur une seule.
API sémantique large, pas granulaire. N’exposez pas fillEmail() + clickSubmit() + readMessage() séparément. Exposez l’intention : subscribe($email), unsubscribe($email), getResultMessage(). Aujourd’hui, le module envoie le formulaire en AJAX et insère le message sans recharger la page ; le jour où l’auteur remplace le formulaire par une modale ou revient à un rechargement complet, seul le corps de submitNewsletterForm() change — les scénarios qui en dépendent tiennent.
Un scénario
Créez tests/prestaflow/Suites/NewsletterSubscribe.php :
<?php
namespace Tests\Suites;
use PrestaFlow\Library\Expects\Expect;
use PrestaFlow\Library\Tests\TestsSuite;
class NewsletterSubscribe extends TestsSuite
{
public function init()
{
$this->importPage('Modules\PsEmailsubscription\NewsletterBlock', domain: 'Tests');
extract($this->pages);
// Suffixe timestamp pour ne pas créer deux fois le même abonnement.
$email = 'qa+' . time() . '@example.test';
$this
->describe('Bloc newsletter ps_emailsubscription')
->it('affiche le bloc sur la home', function () use ($modulesPsEmailsubscriptionNewsletterBlockPage) {
$modulesPsEmailsubscriptionNewsletterBlockPage->goToPage('home');
Expect::that($modulesPsEmailsubscriptionNewsletterBlockPage->isBlockVisible())
->isTheSameAs(true);
})
->it('accepte une nouvelle adresse', function () use ($modulesPsEmailsubscriptionNewsletterBlockPage, $email) {
$modulesPsEmailsubscriptionNewsletterBlockPage->subscribe($email);
// Le texte exact du message dépend de la locale de la boutique : on vérifie
// qu'un message est bien retourné, puis que c'est le message de succès
// (classe alert-success) et pas une erreur (alert-danger).
Expect::that($modulesPsEmailsubscriptionNewsletterBlockPage->getResultMessage())
->isNotEmpty();
Expect::that($modulesPsEmailsubscriptionNewsletterBlockPage->hasSucceeded())
->isTheSameAs(true);
});
}
}
Vous n’avez touché à aucun fichier de ps_emailsubscription. Le module reste à sa place, ses upgrades futurs ne casseront rien de votre côté — ils casseront peut-être les tests, ce qui est précisément ce que vous voulez savoir.
Isolation des données
Un test qui pollue la base est un test qui devient flaky. Deux stratégies suivant les cas :
Suffixe unique par run. L’exemple ci-dessus utilise 'qa+' . time() . '@example.test'. Chaque exécution crée un abonnement différent, aucun conflit d’unicité, la base grossit lentement mais sans jamais faire échouer un test à cause d’un doublon.
Cleanup en fin de suite. Quand le module expose une manière propre de retirer ce qu’on a créé (désabonnement via un lien, suppression via BO), on ajoute un dernier it qui remet à zéro. Pour ps_emailsubscription, c’est le même formulaire : le module lit un champ caché action (0 pour inscrire, 1 pour désinscrire) et retire l’adresse de sa table quand il reçoit 1. C’est ce que fait unsubscribe() dans la Page :
->it('désabonne l\'adresse de test', function () use ($modulesPsEmailsubscriptionNewsletterBlockPage, $email) {
// Page fraîche : pas de message résiduel de l'étape précédente.
$modulesPsEmailsubscriptionNewsletterBlockPage->goToPage('home');
$modulesPsEmailsubscriptionNewsletterBlockPage->unsubscribe($email);
// Libellé d'une boutique en anglais : adaptez-le à la langue de votre recette.
Expect::that($modulesPsEmailsubscriptionNewsletterBlockPage->getResultMessage())
->contains('Unsubscription successful');
})
Attention : si l’étape principale échoue, l’étape de cleanup peut ne pas s’exécuter — d’où l’intérêt de doubler avec la stratégie « suffixe unique ». Les deux se complètent, elles ne s’opposent pas.
Pinner la version du module
Dimension nouvelle par rapport à un module dont vous êtes l’auteur : le module tiers évolue indépendamment de vos tests. Si votre Page repose sur .block_newsletter dans la version installée aujourd’hui et que la suivante renomme la classe, vos tests tombent sans que vous ayez touché à quoi que ce soit.
Deux garde-fous à mettre en place :
- Documenter la version testée dans le
READMEdu repo de QA. La version du module + la version de PrestaShop + la version du thème. Trois lignes. - Figer la version installée sur la boutique de recette. Si le module est distribué via Composer,
composer require vendor/module-name:x.y.zdans le repo de la boutique. Si c’est un ZIP téléchargé depuis l’Addons ou l’éditeur, garder ce ZIP dans un stockage interne et documenter sa provenance — les distributeurs remplacent silencieusement les téléchargements par de nouvelles versions.
Quand la mise à jour du module arrive, vous relancez la suite de tests sur la nouvelle version avant de la déployer en prod. C’est très exactement le rôle que l’article 2 (l’Action GitHub) automatisait — l’idée s’applique à l’identique ici.
Plusieurs modules dans le même projet de QA
Quand vous couvrez cinq à dix modules d’une même boutique, l’organisation qui tient est un dossier de Pages par module sous tests/prestaflow/Pages/v8/Modules/, et une ou plusieurs suites par module dans tests/prestaflow/Suites/ :
tests/prestaflow/
├─ Pages/v8/Modules/
│ ├─ PsEmailsubscription/NewsletterBlock/Page.php
│ ├─ Contactform/ContactPage/Page.php
│ └─ Blockreassurance/ReassuranceBlock/Page.php
└─ Suites/
├─ NewsletterSubscribe.php
├─ ContactSend.php
└─ ReassuranceBlockVisible.php
Chaque module vit dans sa bulle, les suites s’exécutent indépendamment, un module qui casse ne fait tomber que ses propres suites.
Notes
Dans la Série PrestaFlow — article 3 sur 23