Framework de tests d'acceptation pour applications web
BibliothĂšque PHP pour contrĂŽler des navigateurs web et tester des interactions utilisateur
Mink est une couche d'abstraction qui permet d'interagir avec différents navigateurs (réels ou headless) via une API.
Simuler le comportement d'un utilisateur réel : cliquer sur des liens, remplir des formulaires, naviguer entre les pages, vérifier le contenu affiché.
| Driver | Type | JavaScript | Utilisation |
|---|---|---|---|
| GoutteDriver | Headless HTTP | â Non | Tests rapides, crawling |
| BrowserKitDriver | Symfony Client | â Non | Tests Symfony internes |
| Selenium2Driver | Navigateur rĂ©el | â Oui | Tests JS, interactions complexes |
| ChromeDriver | Chrome/Chromium | â Oui | Tests Chrome headless |
# Installation de Mink
composer require --dev behat/mink
composer require --dev behat/mink-goutte-driver
composer require --dev behat/mink-browserkit-driver
composer require --dev behat/mink-selenium2-driver
# Installation de ChromeDriver
composer require --dev dmore/chrome-mink-driver
# Télécharger le binaire ChromeDriver depuis:
# https://chromedriver.chromium.org/
use Behat\Mink\Mink;
use Behat\Mink\Session;
use DMore\ChromeDriver\ChromeDriver;
// Configuration de la session ChromeDriver
$mink = new Mink([
'chrome' => new Session(
new ChromeDriver('http://localhost:9222', null, 'http://localhost')
),
]);
$mink->setDefaultSessionName('chrome');
$session = $mink->getSession();
$session->start();
// Votre test
$session->visit('https://example.com');
$page = $session->getPage();
$page->findButton('Submit')->click();
chrome --headless --remote-debugging-port=9222
use Behat\Mink\Mink;
use Behat\Mink\Session;
use Behat\Mink\Driver\GoutteDriver;
// Création de la session
$mink = new Mink([
'goutte' => new Session(new GoutteDriver())
]);
// Définir la session par défaut
$mink->setDefaultSessionName('goutte');
// Récupérer la session
$session = $mink->getSession();
// Démarrer la session
$session->start();
// ArrĂȘter la session
$session->stop();
// Visiter une URL
$session->visit('https://example.com');
// Récupérer l'URL actuelle
$currentUrl = $session->getCurrentUrl();
// Recharger la page
$session->reload();
// Navigation historique
$session->back();
$session->forward();
// Récupérer la page courante
$page = $session->getPage();
// Récupérer le code de statut HTTP
$statusCode = $session->getStatusCode();
// Gérer les cookies
$session->setCookie('name', 'value');
$cookie = $session->getCookie('name');
// Sélecteur CSS (le plus courant)
$page = $session->getPage();
// Trouver un élément unique
$element = $page->find('css', '.my-class');
$button = $page->find('css', '#submit-btn');
$link = $page->find('css', 'a.menu-link');
// Trouver plusieurs éléments
$items = $page->findAll('css', 'ul.products li');
// Vérifier l'existence
if ($element) {
$text = $element->getText();
$html = $element->getHtml();
}
// Sélecteur XPath (plus puissant)
// Trouver par texte exact
$element = $page->find('xpath', '//button[text()="Envoyer"]');
// Trouver par texte partiel
$element = $page->find('xpath', '//a[contains(text(), "Lire")]');
// Trouver par attribut
$input = $page->find('xpath', '//input[@name="email"]');
// Combinaisons complexes
$element = $page->find(
'xpath',
'//div[@class="card"]//h2[contains(text(), "Titre")]'
);
// Sélecteurs nommés (plus lisibles)
// Trouver un lien par son texte
$link = $page->findLink('Connexion');
// Trouver un bouton par son texte ou ID
$button = $page->findButton('Valider');
// Trouver un champ de formulaire
$field = $page->findField('email');
$field = $page->findField('Votre email'); // Par label
// Trouver par ID
$element = $page->findById('my-element');
// Cliquer sur un élément
$button = $page->findButton('Valider');
$button->click();
// Cliquer sur un lien
$link = $page->findLink('En savoir plus');
$link->click();
// Méthode raccourcie
$page->clickLink('Connexion');
// Double-clic (nécessite un driver JS)
$element = $page->find('css', '.item');
$element->doubleClick();
// Clic droit
$element->rightClick();
// Remplir un champ texte
$page->fillField('email', '[email protected]');
$page->fillField('password', 'secret123');
// Cocher une checkbox
$page->checkField('terms');
// Décocher une checkbox
$page->uncheckField('newsletter');
// Sélectionner dans un select
$page->selectFieldOption('country', 'France');
// Select multiple
$page->selectFieldOption('colors', ['red', 'blue']);
// Soumettre un formulaire
$page->pressButton('Envoyer');
// Upload de fichier
$field = $page->findField('avatar');
$field->attachFile('/path/to/file.jpg');
// Hover (survol souris - nécessite driver JS)
$element = $page->find('css', '.menu-item');
$element->mouseOver();
// Focus sur un élément
$element->focus();
// Appuyer sur une touche
$element->keyPress('Enter');
$element->keyDown('Shift');
$element->keyUp('Shift');
// Drag & Drop (nécessite driver JS)
$source = $page->find('css', '.draggable');
$target = $page->find('css', '.droppable');
$source->dragTo($target);
// Vérifier le contenu de la page
$page = $session->getPage();
// Vérifier la présence de texte
if ($page->hasContent('Bienvenue')) {
// Le texte est présent
}
// Assertions avec WebAssert (recommandé)
use Behat\Mink\WebAssert;
$assert = new WebAssert($session);
// Vérifier le code HTTP
$assert->statusCodeEquals(200);
// Vérifier la présence de texte
$assert->pageTextContains('Connexion réussie');
$assert->pageTextNotContains('Erreur');
// Vérifier l'URL
$assert->addressEquals('/dashboard');
$assert->addressMatches('/\/user\/\d+/');
// Vérifier la présence d'éléments
$assert->elementExists('css', '.alert-success');
$assert->elementNotExists('css', '.alert-danger');
// Compter les éléments
$assert->elementsCount('css', 'ul.products li', 10);
// Vérifier le texte d'un élément
$assert->elementContains('css', 'h1', 'Titre principal');
$assert->elementTextContains('css', '.price', 'âŹ');
// Vérifier les attributs
$assert->elementAttributeContains('css', 'a.active', 'href', '/home');
// Vérifier la visibilité
$element = $page->find('css', '.modal');
if ($element->isVisible()) {
// L'élément est visible
}
// Vérifier les champs de formulaire
$assert->fieldExists('email');
$assert->fieldNotExists('fake-field');
// Vérifier la valeur d'un champ
$assert->fieldValueEquals('email', '[email protected]');
$assert->fieldValueNotEquals('password', '');
// Vérifier qu'une checkbox est cochée
$assert->checkboxChecked('terms');
$assert->checkboxNotChecked('newsletter');
// Vérifier les boutons
$assert->buttonExists('Valider');
// Vérifier les liens
$assert->linkExists('Mot de passe oublié ?');
// Attendre qu'un élément soit visible
$page->waitFor(5000, function ($page) {
return $page->find('css', '.loaded')->isVisible();
});
// Attendre qu'un élément apparaisse
$session->wait(5000, "document.querySelector('.modal') !== null");
// Attendre la fin d'une animation
$session->wait(3000, "jQuery.active == 0");
// Attendre avec timeout personnalisé
$session->wait(10000, "document.readyState === 'complete'");
// Exécuter du JavaScript
$result = $session->evaluateScript('return document.title;');
// Exécuter sans retour
$session->executeScript('window.scrollTo(0, document.body.scrollHeight);');
// Manipuler le DOM
$session->executeScript("
document.querySelector('.element').style.display = 'block';
");
// Déclencher des événements
$session->executeScript("
var event = new Event('change');
document.querySelector('#my-input').dispatchEvent(event);
");
// Récupérer des données
$data = $session->evaluateScript('
return JSON.stringify({
url: window.location.href,
cookies: document.cookie
});
');
// GĂ©rer les requĂȘtes AJAX
// Cliquer et attendre la réponse AJAX
$button = $page->findButton('Charger plus');
$button->click();
// Attendre que jQuery termine
$session->wait(5000, 'jQuery.active == 0');
// Ou attendre l'apparition du nouveau contenu
$page->waitFor(5000, function ($page) {
$items = $page->findAll('css', '.product-item');
return count($items) > 10;
});
// VĂ©rifier qu'une requĂȘte AJAX s'est terminĂ©e
$session->wait(5000, "
typeof window.ajaxComplete !== 'undefined' && window.ajaxComplete === true
");
use Behat\Mink\Mink;
use Behat\Mink\Session;
use DMore\ChromeDriver\ChromeDriver;
use Behat\Mink\WebAssert;
class LoginTest extends TestCase
{
private Mink $mink;
private Session $session;
protected function setUp(): void
{
// Configuration ChromeDriver
$this->mink = new Mink([
'chrome' => new Session(
new ChromeDriver('http://localhost:9222', null, 'http://localhost')
),
]);
$this->mink->setDefaultSessionName('chrome');
$this->session = $this->mink->getSession();
$this->session->start();
}
public function testSuccessfulLogin(): void
{
// AccĂšde Ă la page de connexion
$this->session->visit('http://localhost/login');
$page = $this->session->getPage();
// Vérifie qu'on est sur la bonne page
$assert = new WebAssert($this->session);
$assert->elementTextContains('css', 'h1', 'Connexion');
// Remplit le formulaire
$page->fillField('_username', '[email protected]');
$page->fillField('_password', 'password123');
$page->checkField('_remember_me');
// Soumet le formulaire
$page->pressButton('Se connecter');
// Attend la redirection
$this->session->wait(5000, "document.querySelector('.dashboard') !== null");
// Vérifie qu'on est bien connecté
$assert->elementExists('css', '.user-menu');
$assert->elementTextContains('css', '.welcome-message', 'Bienvenue');
// Vérifie l'URL
$assert->addressMatches('/\/dashboard/');
}
public function testFailedLogin(): void
{
$this->session->visit('http://localhost/login');
$page = $this->session->getPage();
$assert = new WebAssert($this->session);
// Remplit avec de mauvais identifiants
$page->fillField('_username', '[email protected]');
$page->fillField('_password', 'wrongpassword');
$page->pressButton('Se connecter');
// Attend et vérifie le message d'erreur
$this->session->wait(5000, "document.querySelector('.alert-danger') !== null");
$assert->elementTextContains('css', '.alert-danger', 'Identifiants invalides');
// Vérifie qu'on est toujours sur /login
$assert->addressMatches('/\/login/');
}
protected function tearDown(): void
{
$this->session->stop();
}
}
// Capturer un screenshot (avec Selenium2Driver ou ChromeDriver)
$screenshot = $session->getDriver()->getScreenshot();
file_put_contents('/path/to/screenshot.png', base64_decode($screenshot));
// Screenshot sur échec de test
protected function onNotSuccessfulTest(Throwable $t): void
{
if ($this->session) {
$screenshot = $this->session->getDriver()->getScreenshot();
file_put_contents(
sprintf('/tmp/test-failure-%s.png', date('Y-m-d-H-i-s')),
base64_decode($screenshot)
);
}
throw $t;
}
// RĂ©cupĂ©rer tous les noms de fenĂȘtres
$windowNames = $session->getWindowNames();
// Changer de fenĂȘtre
$session->switchToWindow($windowNames[1]);
// Ouvrir un lien dans un nouvel onglet puis switcher
$link = $page->findLink('Ouvrir');
$link->click();
$session->switchToWindow($session->getWindowNames()[1]);
// Accepter une alerte
$session->getDriver()->getWebDriverSession()->accept_alert();
// Annuler une confirmation
$session->getDriver()->getWebDriverSession()->dismiss_alert();
// Récupérer le texte de l'alerte
$alertText = $session->getDriver()->getWebDriverSession()->getAlert_text();