Aller au contenu principal

Introduction à Behat

Apprenez à utiliser Behat, le framework open source de Behavior-Driven Development (BDD) pour PHP. Cette introduction couvre l'écriture de tests fonctionnels puissants en langage naturel et la mise en place de votre premier projet.

· 16 minutes de lecture
Voir la source
Christophe
Amoureux de Markdown, WSL et Docker ~ Curieux insatiable.
Cet article existe aussi en anglais — lire la version originale.

Traduction automatique

Cette traduction a été produite par une IA. Elle peut contenir des contresens ou des maladresses, en particulier sur les termes techniques. En cas de doute, consultez l'article de référence en anglais — c'est lui qui fait foi.

Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.

Introduction à Behat

⚡
TL;DR

Ce tutoriel présente le Behavior-Driven Development (BDD) en PHP avec Behat et Docker. Il couvre la mise en place d'un environnement de test avec Chrome et Mink, l'écriture de scénarios Gherkin et l'implémentation des définitions d'étapes pour automatiser les tests fonctionnels d'applications web.

Lisez aussi mon article sur Cypress

Au travail, je fais partie d'une équipe de développeurs PHP qui construit ensemble une très grosse application propriétaire avec une interface web.

Cette application est développée avec le framework Laravel et nous utilisons PHPUnit et Pest pour exécuter nos tests unitaires (voir Écrire des tests unitaires PHP avec Pest).

Mais en plus des tests unitaires, nous mettons aussi en place un outil qui simule des actions sur notre interface : accéder à la page de login, saisir un identifiant et un mot de passe, simuler le clic sur le bouton « login », attendre que notre écran principal s'affiche puis, sur la nouvelle page, vérifier un tas de choses / effectuer un tas de tâches.

C'est un peu comme demander à un humain de rejouer des scénarios encore et encore, chaque jour, pour s'assurer que nous n'avons pas introduit de régression dans nos derniers développements — par exemple une belle nouvelle fonctionnalité dont les changements de code ont cassé une fonctionnalité existante.

Pour cela, nous utilisons Behat. C'est une dépendance PHP composer que vous pouvez ajouter à votre projet (ou à un nouveau). L'idée est de pouvoir écrire des assertions en anglais (ou en français) courant, du genre « je vais sur le site ABC puis je clique sur l'entrée de menu blog et je devrais recevoir une liste d'articles » ou « dans la zone de recherche, je tape Docker puis je clique sur le bouton de recherche et je devrais recevoir des articles portant le tag Docker ».

Dans cet article, nous allons en apprendre plus sur Behat et voir comment l'utiliser pour un premier test.

Comme on peut le lire sur le site de Behat, Behat est un framework open source de Behavior-Driven Development pour PHP. C'est un outil qui vous aide à livrer un logiciel qui compte, grâce à une communication continue, une découverte délibérée et l'automatisation des tests. Cela veut dire que vous pouvez commencer à écrire vos scénarios avant même de coder. Vous pouvez demander à votre client d'écrire ses assertions en langage courant. Ensuite, le développeur se met à coder et, une fois terminé, il lui suffit d'exécuter les scénarios écrits pour s'assurer que le logiciel répond aux exigences du client.

Introduction au BDD - Behavior-Driven Development​

Imaginez que vous êtes un client et que vous demandez à un développeur de créer un nouveau site web. Dans votre cahier des charges, vous lui écrivez des choses comme :

  • En tant que visiteur, je voudrais une entrée de menu qui, une fois cliquée, va (faire ceci) ;
  • En tant que visiteur, je veux pouvoir accéder à un moteur de recherche qui me permet de faire une sélection dans une catégorie d'articles puis, dans cette catégorie, de lancer une recherche sur le mot (un mot). Une liste d'articles sur ce thème s'affiche alors ;
  • En tant que gestionnaire du site, je veux pouvoir me connecter à une interface d'administration et, après un login réussi, je dois voir les options (liste des fonctionnalités) ;
  • En tant que gestionnaire du site, je dois pouvoir ajouter un nouvel article où je dois spécifier un titre, une catégorie, une image principale et un texte. Une fois enregistré, je dois voir le nouvel article dans la liste des articles et, en triant la liste sur la date/heure de création, il doit être le premier de la liste ;
  • (et beaucoup plus)

En termes Behat, ces phrases s'appellent des scénarios. Vous pouvez les écrire avant même que le site existe. Et pendant les étapes de codage, le développeur exécutera vos scénarios de temps en temps. Au premier lancement, tous les scénarios vont échouer (le site n'existe pas ; les fonctions demandées ne sont pas encore développées). Le développeur travaillera sur l'une ou l'autre fonctionnalité ; il relancera les tests BDD et, au fil du projet, les scénarios passeront au vert jusqu'à la fin du projet, quand tous les scénarios seront réussis et que le projet pourra vous être envoyé pour recette.

Résultat​

Un scénario est juste un fichier .feature écrit en anglais courant, une ligne par étape :

Feature: Clicking on the Blog menu item should give me the list of articles
Scenario: I click on the Blog menu
Given I am on "https://www.avonture.be"
Then I click on the "Blog" menu item
Then I should be on "/blog"

Ce scénario de test vérifie qu'en cliquant sur l'élément de menu Blog depuis la page d'accueil, l'utilisateur est redirigé vers la page /blog. C'est un test d'acceptation qui valide le comportement de navigation du site web.

Ce scénario utilise trois étapes :

  • Given I am on "https://www.avonture.be"
  • Then I click on the "Blog" menu item et
  • Then I should be on "/blog".

Avant d'écrire une seule ligne de PHP, demandez simplement à Behat de l'exécuter — il comprend déjà le scénario et nous dit exactement ce qui manque encore :

Premier lancement

C'est tout l'argument : des étapes en langage courant, analysées et suivies par Behat, et du PHP uniquement pour faire en sorte que chaque étape fasse réellement quelque chose. Le reste de cet article construit ce PHP.

Mettre en place le projet​

Pour cet article, nous allons créer un nouveau projet Behat et valider ce scénario : « En tant que visiteur, je veux pouvoir visiter le site XYZ ; cliquer sur un élément de navigation appelé "Blog". Le site m'affiche alors la liste des articles de blog. »

Comme vous le savez si vous lisez régulièrement mon blog, je bois, mange et respire Docker, ce qui veut dire que je n'ai plus ni PHP, ni composer, ni Apache sur mon ordinateur.

Donc, pour notre nouveau défi ci-dessus (que j'accepte avec plaisir), je vais créer un petit projet avec Docker, PHP 8.2 et composer.

C'est parti...

  • D'abord, créons un nouveau répertoire : mkdir /tmp/behat && cd $_.
  • Là, créons un fichier appelé Dockerfile avec le contenu ci-dessous. Ce script est déjà gros mais, comme ça, nous aurons tout ce qu'il faut dès maintenant.
# cspell:disable
# The user ID and name and group ID of the user to create in our image
ARG DOCKER_UID=1000
ARG DOCKER_GID=1000
ARG USERNAME=johndoe
# The version of Chrome we'll install
ARG CHROME_VERSION=115.0.5763.0
# This is a PHP project
FROM php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends git jq zip unzip wget gnupg \
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/*
# We'll create a new user with the same uid/gid than ours, on our host machine.
ARG DOCKER_UID
ARG DOCKER_GID
ARG USERNAME
RUN groupadd --gid ${DOCKER_GID} "${USERNAME}" \
&& useradd --home-dir /home/"${USERNAME}" --create-home --uid ${DOCKER_UID} \
--gid ${DOCKER_GID} --shell /bin/sh --skel /dev/null "${USERNAME}"
# Download and install Chrome
ARG CHROME_VERSION
ARG DOWNLOAD_URL="https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
# Download Chrome (the browser and the driver)
RUN wget --no-check-certificate --no-verbose -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update -yqq \
&& apt-get install -y --no-install-recommends -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 \
libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 \
libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 \
libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
libxss1 libxtst6 ca-certificates fonts-liberation libnss3 lsb-release \
xdg-utils \
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/* \
# --- Download the browser ---
&& ZIP_URL=$(curl $DOWNLOAD_URL | jq -r ".versions[] | select(.version==\""$CHROME_VERSION"\").downloads.chrome[] | select(.platform==\""linux64"\") .url") \
&& wget --no-check-certificate --no-verbose -O /tmp/chrome_browser.zip $ZIP_URL \
&& printf "\e[0;105m%s\e[0;0m\n" "Using chromedriver $(/usr/local/bin/chrome/chromedriver --version)" \
# Unzip and create the /usr/local/bin/chromedriver executable (-j means don't create a subfolder with the name of the archive; unzip in the folder directly)
&& unzip -j /tmp/chrome_browser.zip -d /usr/local/bin/chrome \
&& rm -f /tmp/chrome_browser.zip \
&& ls -alh /usr/local/bin/chrome \
&& chmod +x /usr/local/bin/chrome/chrome \
# --- Download the driver ---
&& ZIP_URL=$(curl $DOWNLOAD_URL | jq -r ".versions[] | select(.version==\""$CHROME_VERSION"\").downloads.chromedriver[] | select(.platform==\""linux64"\") .url") \
&& wget --no-check-certificate --no-verbose -O /tmp/chrome_driver.zip $ZIP_URL \
# Unzip and create the /usr/local/bin/chromedriver executable (-j means don't create a subfolder with the name of the archive; unzip in the folder directly)
&& unzip -j /tmp/chrome_driver.zip -d /usr/local/bin/chrome \
&& rm -f /tmp/chrome_driver.zip \
&& chmod +x /usr/local/bin/chrome/chrome \
# Make some cleanup
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/*
# Get latest composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Where we'll put our script
RUN mkdir /opt/behat
# Set working directory
WORKDIR /opt/behat

Ce Dockerfile crée une image Docker basée sur PHP 8.2 configurée pour exécuter des tests avec Chrome. Il installe les dépendances système, crée un utilisateur non-root avec des identifiants correspondant à l'hôte, télécharge et configure Chrome ainsi que son driver, puis ajoute Composer et prépare un répertoire de travail pour Behat.

  • Créez un fichier appelé compose.yaml avec ce contenu :
Vos valeurs pour cette page
défaut
S'applique aux commandes de cette pageEnregistré uniquement sur cet appareil
# cspell:disable
services:
app:
build:
context: .
args:
# Please update IDs below if you've another values by running "id -u" (UID) and "id -g" (GID) in your Linux console
- DOCKER_UID=1000
- DOCKER_GID=1000
# The name of the user in Docker; didn't have any importance
- USERNAME=johndoe
container_name: php-app
working_dir: /opt/behat
volumes:
# We'll synchronize our local project folder (on our machine) with the Docker container
- ./:/opt/behat

Ce fichier de configuration Docker Compose définit un service appelé app qui construit une image Docker à partir du répertoire courant. Le service configure des arguments de build pour les identifiants utilisateur Linux (UID et GID), définit un nom d'utilisateur personnalisé, et monte le répertoire du projet local dans le conteneur pour synchroniser les fichiers entre la machine hôte et Docker.

  • Lancez docker compose up --detach pour créer votre container Docker

Nous allons vérifier que le driver Chrome est bien installé en lançant docker compose exec -u $(id -u):$(id -g) app /usr/local/bin/chrome/chromedriver --version. Idem pour le navigateur Chrome : docker compose exec -u $(id -u):$(id -g) app /usr/local/bin/chrome/chrome --version. Parfait ! Voir ces valeurs signifie que Chrome est prêt à être utilisé.

Versions de Chrome

Ok, encore une chose : nous devons créer notre fichier composer.json puisqu'il faut inclure la dépendance PHP Behat.

  • Lancez docker compose exec -u $(id -u):$(id -g) app /bin/bash pour démarrer un shell interactif dans votre container Docker en étant vous-même (c'est-à-dire qu'avec -u $(id -u):$(id -g), les fichiers et dossiers créés dans Docker vous appartiendront),

  • Lancez composer init pour démarrer l'assistant composer ; saisissez les valeurs que vous voulez, par exemple

    • Package name : johndoe/behat,
    • Description : Introduction to Behat,
    • Author : John Doe,
    • Minimum Stability : appuyez simplement sur Entrée,
    • Package Type : project,
    • License : appuyez simplement sur Entrée,
    • Composer vous demandera si vous voulez définir des dépendances et des dépendances de dev ; répondez no deux fois pour l'instant,
    • La question suivante concerne PSR-4, appuyez simplement sur Entrée,
    • Enfin, répondez yes au message de confirmation.

Maintenant, si vous êtes curieux, vous verrez que vous avez un nouveau fichier appelé composer.json et deux dossiers appelés src et vendor.

Composer a été installé

Installons Behat​

Comme indiqué dans la documentation How to install?, il suffit d'exécuter composer require --dev behat/behat:^3 pour installer Behat comme dépendance de dev.

Information

Assurez-vous d'être toujours dans une session Bash interactive dans le container Docker avant de lancer composer require --dev behat/behat:^3. Une session Bash interactive se démarre en lançant docker compose exec -u $(id -u):$(id -g) app /bin/bash.

Installer Behat

Une fois installé, vous pouvez démarrer Behat en lançant vendor/bin/behat mais, pour l'instant, vous obtiendrez une erreur et c'est parfaitement normal puisque nous devons d'abord écrire notre premier scénario.

FeatureContext introuvable

Comme indiqué dans la documentation officielle, lancez simplement vendor/bin/behat --init pour créer les fichiers minimums requis.

Exécution de behat init

Maintenant, si vous regardez votre dossier, vous voyez un nouveau dossier appelé features/bootstrap avec un fichier appelé FeatureContext.php. La capture ci-dessous illustre à quoi ressemble le projet dans mon éditeur VSCode à ce stade du tutoriel :

FeatureContext dans vscode

Écrire vos étapes en PHP​

Cela doit être placé dans un fichier portant l'extension .feature dans le dossier features ; créons le fichier Blog.feature (montré au début de cet article) avec ce contenu :

Le fichier Blog.feature

Pensez à installer `Cucumber (Gerkin) Full Support`

Si vous utilisez Visual Studio Code, vous pouvez installer Cucumber (Gerkin) Full Support pour obtenir la coloration et une meilleure intégration dans VSCode, comme l'autocomplétion.

Maintenant que nous avons notre scénario, nous devons apprendre à Behat comment traduire ces phrases (c'est le langage Gherkin) dans notre code PHP.

Ouvrez maintenant le fichier features/bootstrap/FeatureContext.php dans votre éditeur préféré :

Édition du fichier FeatureContext.php

Comme vous le voyez, c'est le fichier par défaut créé plus tôt. Nous devons y écrire du code PHP, mais comment ?

Retournez dans votre console Bash interactive et lancez vendor/bin/behat --dry-run --append-snippets. Cela demande à Behat de créer les étapes pour vous, automagiquement.

Append snippets

Tapez 1 pour demander à Behat d'utiliser votre fichier FeatureContext.php. Vous obtiendrez alors une confirmation à l'écran :

u features/bootstrap/FeatureContext.php - `I am on "https://www.avonture.be"` definition added
u features/bootstrap/FeatureContext.php - `I click on the "Blog" menu item` definition added
u features/bootstrap/FeatureContext.php - `I should be on "/blog"` definition added

Ouvrez à nouveau le fichier features/bootstrap/FeatureContext.php dans votre éditeur préféré :

Nous avons maintenant nos étapes

Sympa, non ? Behat a regardé notre fichier Blog.feature et a créé autant de méthodes dans notre code PHP que nous avions de phrases dans notre scénario, et chaque méthode est associée à sa phrase (regardez le commentaire PHP avant chaque méthode).

Et maintenant, avant même de commencer à coder, demandons à Behat d'exécuter notre scénario ; lancez vendor/bin/behat dans la console — c'est la sortie « Premier lancement » montrée au début de cet article.

Wow ! Jusqu'ici, parfait ! Nous avons donc demandé à Behat d'exécuter notre scénario et il sait que nous avons trois étapes et qu'il nous reste à écrire le code associé en PHP (d'où le message TODO: write pending definition en jaune).

Donnons-nous les moyens de faire au mieux le plus vite possible, sans réinventer la roue si quelqu'un d'autre l'a déjà fait. Installons deux nouvelles dépendances. Dans votre console, lancez composer require --dev friends-of-behat/mink et composer require --dev dmore/behat-chrome-extension.

friends-of-behat/mink est une très chouette dépendance qui arrive avec des étapes déjà prédéfinies : faire un login, naviguer vers des pages, vérifier que la page courante est ... Et en utilisant friends-of-behat/mink, la vie sera plus simple et nous n'aurons pas à réinventer la roue.

Utiliser Mink​

Maintenant que Mink est installé, allez dans votre éditeur, ouvrez le fichier features/bootstrap/FeatureContext.php et remplacez la ligne class FeatureContext implements Context par class FeatureContext extends \Behat\MinkExtension\Context\MinkContext.

Utilisation de MinkContext

Ce petit changement nous donne beaucoup de pouvoir car notre code utilise désormais Mink et bénéficie donc de toutes ses méthodes existantes.

Pour obtenir la liste des méthodes existantes, lançons clear ; vendor/bin/behat -di dans votre console :

Affichage des définitions

Vous avez vu ? Nous avons nos trois étapes (les trois premières affichées) puis des étapes supplémentaires venant de Mink. Et vous pouvez scroller longtemps : il y a déjà beaucoup d'étapes que Mink vous permet de réutiliser.

Relancez vendor/bin/behat dans la console :

Correspondance ambiguë

Nous obtenons l'erreur Ambiguous match of ... sur la toute première étape, notre étape I am on https://www.avonture.be. Ambiguë signifie que nous essayons d'ajouter du code PHP dans notre contexte alors que cette étape est déjà définie (par Mink). Donc, supprimez-la simplement : éditez votre fichier FeatureContext.php, sélectionnez public function iAmOn et supprimez toute la fonction.

Supprimer la méthode iAmOn

En relançant vendor/bin/behat, vous aurez la même erreur pour iShouldBeOn ; supprimez donc aussi cette fonction.

Lancez clear ; vendor/bin/behat une fois de plus et nous obtenons maintenant une nouvelle erreur :

Instance Mink non définie

Installer l'extension Mink​

Nous devons maintenant référencer behat-chrome-extension dans un fichier appelé behat.yml.

Créez le fichier behat.yml à la racine de votre projet, avec ce contenu :

default:
extensions:
DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
Behat\MinkExtension:
browser_name: chrome
base_url: https://www.avonture.be
sessions:
default:
chrome:
api_url: http://0.0.0.0:9222

Ce fichier de configuration Behat définit les extensions et paramètres pour exécuter des tests d'acceptation dans un navigateur Chrome. Il active l'extension ChromeExtension personnalisée et configure MinkExtension pour communiquer avec une instance Chrome locale en mode debugging sur le port 9222, en testant contre le site avonture.be.

Éditez également le fichier features/bootstrap/FeatureContext.php, supprimez tout et remplacez par le contenu ci-dessous.

Nous avons modifié quelques instructions use pour ajouter les bibliothèques Mink (et retirer celles qui ne servent plus). Nous avons aussi ajouté une propriété privée $mink et quelques lignes dans le __constructor.

Pensez à adapter l'url `https://www.avonture.be` à votre site

<?php
use Behat\Mink\Mink;
use Behat\Mink\Session;
use DMore\ChromeDriver\ChromeDriver;
use Behat\Behat\Tester\Exception\PendingException;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends \Behat\MinkExtension\Context\MinkContext
{
private Behat\Mink\Mink $mink;
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
$this->mink = new Mink(
[
'browser' => new Session(
new ChromeDriver(
'http://0.0.0.0:9222',
null,
"https://www.avonture.be" // <-- Think to update to the site you wish to test
)
)
]
);
}
/**
* @Then I click on the :arg1 menu item
*/
public function iClickOnTheMenuItem($arg1)
{
throw new PendingException();
}
}

Ce fichier définit une classe de contexte FeatureContext pour les tests Behat qui contrôlent un navigateur Chrome. Elle initialise une instance Mink avec un driver Chrome connecté à un serveur de débogage distant, permettant d'automatiser les tests d'interface utilisateur sur un site web spécifique.

En relançant vendor/bin/behat, nous avons maintenant une autre erreur :

Could not fetch version information

Place à Chrome​

Créez un fichier appelé run.sh avec ce contenu :

#!/usr/bin/env bash
clear
# We'll start Chrome on URL http://0.0.0.0:9222
# If this URL has to be modified, think to update the constructor of FeatureContext.php too
# Note: /usr/bin/google-chrome-stable has been installed in our Dockerfile
nohup /usr/local/bin/chrome/chrome --headless --remote-debugging-address=0.0.0.0 \
--remote-debugging-port=9222 --no-sandbox --window-size="1920,1080" --disable-dev-shm-usage \
--disable-extensions --no-startup-window --no-first-run --no-pings > /dev/null 2>&1 &
# Get the process ID of chrome so we can kill the process when we've finished
chromePID=$!
# shellcheck disable=SC2048,SC2086
php -d memory_limit=-1 "vendor/bin/behat" --config=behat.yaml
if ((chromePID > 0)); then
kill "${chromePID}" > /dev/null 2>&1
fi

Ce script démarre Chrome en mode headless (sans interface graphique) sur le port 9222 pour permettre à Behat (un framework de test) de piloter le navigateur, puis exécute les tests Behat avant de terminer le processus Chrome.

Puis rendez le fichier exécutable en lançant chmod +x ./run.sh.

Vous pouvez maintenant démarrer Behat en lançant ./run.sh dans votre console.

Si tout se passe bien, vous obtiendrez ceci :

Premier lancement

Comme vous le voyez, la ligne Given I am on... est en vert : Behat, grâce à notre driver Chrome, a réussi à atteindre la page.

Ensuite, la ligne Then I click on the "Blog" menu item est en jaune et c'est normal puisque nous n'avons pas encore créé la fonction et, logiquement, Then I should be on "/blog" est en bleu parce que cette phrase n'a pas encore été exécutée (skippée).

Écrire notre méthode iClickOnTheMenuItem​

Retour au fichier bootstrap/FeatureContext.php. Remplacez la méthode iClickOnTheMenuItem par ce code :

/**
* @Then I click on the :menuItem menu item
*/
public function iClickOnTheMenuItem(string $menuItem): void
{
if (!is_dir('.output')) {
mkdir('.output', 0777);
}
// When entering in the method, take a screenshot so we can assert we're on the homepage
file_put_contents('.output/homepage.png', $this->getSession()->getDriver()->getScreenshot());
throw new PendingException();
}

Cette méthode est un test Behat qui devrait simuler un clic sur un élément de menu, mais elle est actuellement inachevée. Pour l'instant, elle crée un répertoire de sortie s'il n'existe pas, puis sauvegarde une capture d'écran de la page actuelle avant de lever une exception indiquant que le test est en attente d'implémentation.

Comme vous le voyez, nous allons créer un dossier .output et la seule chose que nous faisons est de prendre une capture d'écran.

Juste après avoir relancé ./run.sh dans votre console, vous devriez voir un nouveau dossier .output avec une image :

Nous sommes sur la page d&#39;accueil

Qu'avons-nous fait ? Nous avons vérifié que lorsque Behat exécute notre méthode iClickOnTheMenuItem, Chrome accède bien à notre page d'accueil. Nous pouvons maintenant simuler un clic sur l'entrée de menu voulue : le menu Blog stocké dans notre paramètre $menuItem.

Remplacez la méthode par ce nouveau code :

/**
* @Then I click on the :menu menu item
*/
public function iClickOnTheMenuItem(string $menu): void
{
if (!is_dir('.output')) {
mkdir('.output', 0777);
}
// When entering in the method, take a screenshot so we can assert we're on the homepage
file_put_contents('.output/homepage.png', $this->getSession()->getDriver()->getScreenshot());
/**
* @var $elements Behat\Mink\Element\NodeElement
*/
$elements = $this->getSession()->getPage()->findAll('css', 'a[class="navbar__item navbar__link"]');
// Now we'll get all links from our navigation bar. $elements will contain more than one link in our case.
// The CSS selector has been written using our Web developer console; in Edge or Chromium for instance.
// Loop any elements (our navigation links) and when we've found the one having `Blog` as text, click on it
foreach ($elements as $element) {
if ($element->getText() === $menu) {
$element->click();
}
}
// Give one second to the website to handle the click and, so, to navigate to, as we expect it, the `/blog` path
sleep(1);
// Take a second image, now we should be in the list of posts
file_put_contents('.output/iClickOnBlog.png', $this->getSession()->getDriver()->getScreenshot());
}

Cette méthode de test Behat simule un clic sur un élément de menu de navigation. Elle crée d'abord un répertoire de sortie s'il n'existe pas, puis prend une capture d'écran initiale, recherche tous les liens de la barre de navigation avec un sélecteur CSS, clique sur celui dont le texte correspond au paramètre fourni, attend une seconde pour que la navigation se termine, et enregistre une deuxième capture d'écran.

Lancez ./run.sh une fois de plus et bingo !

Succès

C'est un succès parce que le Then I should be on "/Blog" a été vérifié par Mink. Nous n'avons pas besoin d'écrire la méthode Then I should be on, elle est déjà définie par Mink.

Essayons autre chose : retour dans le PHP et remplacez if ($element->getText() === $menu) { par if ($element->getText() === 'Archive') {. Nous ne cliquerons donc plus sur le menu Blog mais sur Archive ; voyons ce qui se passe :

Échec

Et ça échoue ; parfait !

Conclusion​

C'est la fin de ce tutoriel.

Avec Behat et Mink, nous pouvons automatiser les tests fonctionnels.

Nous allons pouvoir écrire des scénarios, simuler des actions utilisateur comme cliquer sur n'importe quel objet de la page, uploader des documents, faire des choses comme insérer un nouvel article de blog puis injecter du contenu dans l'éditeur et, après avoir enregistré l'élément, visiter la page du blog, la trier par date et vérifier que notre nouvel article s'affiche bien et qu'il est le premier de la liste.

Nous pouvons le faire avant que l'application existe (concept du BDD), pendant le codage (pour s'assurer que les fonctionnalités marchent toujours, même après un gros refactoring par exemple) ou pendant tout le cycle de vie (pour s'assurer que l'application fonctionne toujours comme prévu).

Continuez votre exploration grâce à ces sites :

Annexe - Liste des fichiers, leur contenu et comment les exécuter​

Dans ce tutoriel, nous avons manipulé beaucoup de fichiers et de commandes. Pour vous permettre de vérifier que votre version est bien celle utilisée à la fin de cet article, vous trouverez ci-dessous chaque fichier avec son contenu en fin de tutoriel.

Pour rappel, quand tous les fichiers ci-dessous ont été créés sur votre disque, vous devez d'abord créer votre image Docker et un container ; cela se fait en lançant docker compose up --detach dans votre console.

La deuxième commande à lancer est docker compose exec -u $(id -u):$(id -g) app composer install pour installer toutes les dépendances PHP.

Enfin, pour exécuter notre script run.sh, vous devez lancer docker compose exec -u $(id -u):$(id -g) app ./run.sh

behat.yaml​

default:
extensions:
DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
Behat\MinkExtension:
browser_name: chrome
base_url: https://www.avonture.be
sessions:
default:
chrome:
api_url: http://0.0.0.0:9222

Ce fichier configure Behat, un framework de test, pour exécuter des tests dans Chrome. Il active deux extensions : ChromeExtension pour contrôler le navigateur Chrome, et MinkExtension pour gérer les sessions de navigation. Les tests vont s'exécuter contre l'URL https://www.avonture.be en communiquant avec une instance Chrome locale sur le port 9222.

Blog.feature​

Le nom de fichier relatif est features/Blog.feature.

Feature: Clicking on the Blog menu item should gives me the list of articles
Scenario: I click on the Blog menu
Given I am on "https://www.avonture.be"
Then I click on the "Blog" menu item
Then I should be on "/blog"

Ce fichier de test décrit un scénario de comportement utilisateur : vérifier qu'en cliquant sur l'élément de menu « Blog » depuis la page d'accueil, l'utilisateur est redirigé vers la page /blog. C'est un test d'acceptation écrit en Gherkin qui valide un flux de navigation simple.

composer.json​

{
"name": "johndoe/behat",
"description": "Introduction to behat",
"type": "project",
"autoload": {
"psr-4": {
"Johndoe\\Behat\\": "src/"
}
},
"authors": [
{
"name": "John Doe"
}
],
"require-dev": {
"behat/behat": "^3",
"behat/mink-extension": "^2.3",
"dmore/behat-chrome-extension": "^1.4",
"friends-of-behat/mink": "^1.11"
}
}

Ceci est un fichier composer.json qui configure un projet Behat pour les tests de comportement. Il déclare l'espace de noms PHP du projet, liste l'auteur, et spécifie les dépendances de développement nécessaires comme Behat lui-même et ses extensions pour tester des applications web avec Mink et Chrome.

compose.yaml​

# cspell:disable
services:
app:
build:
context: .
args:
# Please update IDs below if you've another values by running "id -u" (UID) and "id -g" (GID) in your Linux console
- DOCKER_UID=1000
- DOCKER_GID=1000
# The name of the user in Docker; didn't have any importance
- USERNAME=johndoe
container_name: php-app
working_dir: /opt/behat
volumes:
# We'll synchronize our local project folder (on our machine) with the Docker container
- ./:/opt/behat

Ce fichier Docker Compose définit un service appelé app qui construit une image Docker à partir du contexte courant et configure un conteneur nommé php-app. Le conteneur synchronise le répertoire du projet local avec /opt/behat dans le conteneur et utilise des identifiants utilisateur personnalisables pour éviter les problèmes de permissions.

Dockerfile​

# cspell:disable
# The user ID and name and group ID of the user to create in our image
ARG DOCKER_UID=1000
ARG DOCKER_GID=1000
ARG USERNAME=johndoe
# The version of Chrome we'll install
ARG CHROME_VERSION=115.0.5763.0
# This is a PHP project
FROM php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends git jq zip unzip wget gnupg \
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/*
# We'll create a new user with the same uid/gid than ours, on our host machine.
ARG DOCKER_UID
ARG DOCKER_GID
ARG USERNAME
RUN groupadd --gid ${DOCKER_GID} "${USERNAME}" \
&& useradd --home-dir /home/"${USERNAME}" --create-home --uid ${DOCKER_UID} \
--gid ${DOCKER_GID} --shell /bin/sh --skel /dev/null "${USERNAME}"
# Download and install Chrome
ARG CHROME_VERSION
ARG DOWNLOAD_URL="https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
# Download Chrome (the browser and the driver)
RUN wget --no-check-certificate --no-verbose -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update -yqq \
&& apt-get install -y --no-install-recommends -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 \
libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 \
libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 \
libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
libxss1 libxtst6 ca-certificates fonts-liberation libnss3 lsb-release \
xdg-utils \
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/* \
# --- Download the browser ---
&& ZIP_URL=$(curl $DOWNLOAD_URL | jq -r ".versions[] | select(.version==\""$CHROME_VERSION"\").downloads.chrome[] | select(.platform==\""linux64"\") .url") \
&& wget --no-check-certificate --no-verbose -O /tmp/chrome_browser.zip $ZIP_URL \
&& printf "\e[0;105m%s\e[0;0m\n" "Using chromedriver $(/usr/local/bin/chrome/chromedriver --version)" \
# Unzip and create the /usr/local/bin/chromedriver executable (-j means don't create a subfolder with the name of the archive; unzip in the folder directly)
&& unzip -j /tmp/chrome_browser.zip -d /usr/local/bin/chrome \
&& rm -f /tmp/chrome_browser.zip \
&& ls -alh /usr/local/bin/chrome \
&& chmod +x /usr/local/bin/chrome/chrome \
# --- Download the driver ---
&& ZIP_URL=$(curl $DOWNLOAD_URL | jq -r ".versions[] | select(.version==\""$CHROME_VERSION"\").downloads.chromedriver[] | select(.platform==\""linux64"\") .url") \
&& wget --no-check-certificate --no-verbose -O /tmp/chrome_driver.zip $ZIP_URL \
# Unzip and create the /usr/local/bin/chromedriver executable (-j means don't create a subfolder with the name of the archive; unzip in the folder directly)
&& unzip -j /tmp/chrome_driver.zip -d /usr/local/bin/chrome \
&& rm -f /tmp/chrome_driver.zip \
&& chmod +x /usr/local/bin/chrome/chrome \
# Make some cleanup
&& apt-get clean \
&& rm -rf /tmp/* /var/lib/apt/lists/*
# Get latest composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Where we'll put our script
RUN mkdir /opt/behat
# Set working directory
WORKDIR /opt/behat

Ce Dockerfile construit une image Docker basée sur PHP 8.2-FPM pour exécuter des tests avec Behat et Chrome. Il crée un utilisateur non-root avec des identifiants spécifiques, installe les dépendances système, télécharge et configure Chrome (navigateur et driver) depuis Google, ajoute Composer, puis prépare le répertoire de travail.

FeatureContext.php​

Le nom de fichier relatif est features/bootstrap/FeatureContext.php.

<?php
use Behat\Mink\Mink;
use Behat\Mink\Session;
use DMore\ChromeDriver\ChromeDriver;
use Behat\Behat\Tester\Exception\PendingException;
/**
* Defines application features from the specific context.
*/
class FeatureContext extends \Behat\MinkExtension\Context\MinkContext
{
private Behat\Mink\Mink $mink;
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
$this->mink = new Mink(
[
'browser' => new Session(
new ChromeDriver(
'http://0.0.0.0:9222',
null,
"https://www.avonture.be" // <-- Think to update to the site you wish to test
)
)
]
);
}
/**
* @Then I click on the :menu menu item
*/
public function iClickOnTheMenuItem(string $menu): void
{
if (!is_dir('.output')) {
mkdir('.output', 0777);
}
// When entering in the method, take a screenshot so we can assert we're on the homepage
file_put_contents('.output/homepage.png', $this->getSession()->getDriver()->getScreenshot());
/**
* @var $elements Behat\Mink\Element\NodeElement
*/
$elements = $this->getSession()->getPage()->findAll('css', 'a[class="navbar__item navbar__link"]');
// Now we'll get all links from our navigation bar. $elements will contain more than one link in our case.
// The CSS selector has been written using our Web developer console; in Edge or Chromium for instance.
// Loop any elements (our navigation links) and when we've found the one having `Blog` as text, click on it
foreach ($elements as $element) {
if ($element->getText() === $menu) {
$element->click();
}
}
// Give one second to the website to handle the click and, so, to navigate to, as we expect it, the `/blog` path
sleep(1);
// Take a second image, now we should be in the list of posts
file_put_contents('.output/iClickOnBlog.png', $this->getSession()->getDriver()->getScreenshot());
}
}

Ce fichier définit un contexte Behat pour tester une application web avec un navigateur Chrome. Il initialise une instance Mink configurée pour piloter Chrome, puis fournit une étape de test qui clique sur un élément de menu de navigation en cherchant le lien correspondant au texte fourni et en capturant des captures d'écran avant et après le clic.

run.sh​

#!/usr/bin/env bash
clear
# We'll start Chrome on URL http://0.0.0.0:9222
# If this URL has to be modified, think to update the constructor of FeatureContext.php too
# Note: /usr/bin/google-chrome-stable has been installed in our Dockerfile
nohup /usr/local/bin/chrome/chrome --headless --remote-debugging-address=0.0.0.0 \
--remote-debugging-port=9222 --no-sandbox --window-size="1920,1080" --disable-dev-shm-usage \
--disable-extensions --no-startup-window --no-first-run --no-pings > /dev/null 2>&1 &
# Get the process ID of chrome so we can kill the process when we've finished
chromePID=$!
# shellcheck disable=SC2048,SC2086
php -d memory_limit=-1 "vendor/bin/behat" --config=behat.yaml
if ((chromePID > 0)); then
kill "${chromePID}" > /dev/null 2>&1
fi

Ce script démarre Chrome en mode sans interface graphique sur le port 9222 pour permettre le débogage à distance, puis lance les tests Behat avec PHP. Après que les tests se terminent, il arrête le processus Chrome.


🦋 Rejoignez la conversation sur BlueskyUn compte Bluesky gratuit est nécessaire pour aimer, repartager ou commenter. Ce bloc interroge l'API publique de Bluesky directement depuis votre navigateur (sans cookie, sans connexion) — votre adresse IP parvient donc aux serveurs de Bluesky dès que cette section se charge.

Chargement des commentaires…

Cet article vous a-t-il été utile ?
Avez-vous suivi les étapes de cet article ?