Panther : un testeur de navigateur et une bibliothèque d’extraction de contenu pour PHP et Symfony

Retrouvez la publication initiale de cet article (EN) ici.

Présentée dernièrement lors d’un lightning talk au Symfony Live Paris 2018 et lors d’un meetup de l’AFUP Hauts de France, Panther est une bibliothèque autonome permettant d’extraire des sites web et d’exécuter des tests fonctionnels de bout en bout (E2E) en utilisant de vrais navigateurs.  

Panther

Panther est extrêmement puissant. Cet outil tire parti du protocole WebDriver du W3C pour gérer des navigateurs tels que Google Chrome ou Firefox. Simple d’utilisation, il implémente les composants BrowserKit et DomCrawler de Symfony et contient toutes les fonctionnalités dont vous avez besoin pour tester vos applications.

Son utilisation vous semblera familière si vous avez déjà créé un test fonctionnel pour une application Symfony : l’API est exactement la même ! Il est important de garder à l’esprit que Panther étant une bibliothèque autonome, elle peut être utilisée dans tous les projets PHP. Panther trouve automatiquement votre installation locale de Chrome et la lance (merci ChromeDriver). Vous n’avez rien d’autre à installer : pas besoin de serveur Selenium ni de pilote obscur. En mode test, Panther démarre automatiquement votre application à l’aide du serveur web intégré de PHP.

Vous pouvez vous concentrer sur la rédaction de vos tests ou scénarios, Panther s’occupe de tout le reste !  Voyons ensemble comment l'installer et l'utiliser : 

Installation

Pour installer Panther dans votre projet, utilisez Composer. Vous pouvez utiliser l’option --dev si vous souhaitez vous servir uniquement de Panther pour les tests et non pour l’extraction de contenu :


composer req symfony/panther:dev-master
composer req --dev symfony/panther:dev-master 

Utilisation de base

<?php

require __DIR__.'/vendor/autoload.php'; // Composer's autoloader

$client = \Symfony\Component\Panther\Client::createChromeClient();
$crawler = $client->request('GET', 'http://api-platform.com'); // Yes, this website is 100% in JavaScript

$link = $crawler->selectLink('Support')->link();
$crawler = $client->click($link);

// Wait for an element to be rendered
$client->waitFor('.support');

echo $crawler->filter('.support')->text();
$client->takeScreenshot('screen.png'); // Yeah, screenshot!

Utilisation des tests

La classe PantherTestCase vous permet de rédiger aisément des tests E2E. Elle démarre automatiquement votre application en utilisant le serveur Web PHP intégré et vous permet de l’explorer à l’aide de Panther. Elle étend le TestCase de PHP Unit et fournit tous les outils de tests auxquels vous êtes habitué·e.

<?php

use Symfony\Component\Panther\PantherTestCase;

class E2eTest extends PantherTestCase
{
    public function testMyApp()
    {
        $client = static::createPantherClient(); // Your app is automatically started using the built-in web server
        $crawler = $client->request('GET', '/mypage');

        $this->assertContains('My Title', $crawler->filter('title')->text()); // You can use any PHPUnit assertion
    }
}

Exécutez ce test de cette manière :

phpunit tests/E2eTest.php

Un félin polymorphe

Si vous souhaitez tester une application Symfony, PantherTestCase étend automatiquement la classe WebTestCase. Cela signifie que vous pouvez facilement créer des tests fonctionnels, qui peuvent exécuter directement le noyau de votre application et accéder à tous vos services existants. Contrairement au client de Panther, le client de test ne supporte pas le JavaScript et la capture d’écran. En parallèle (et même pour les applications qui ne sont pas en Symfony), Panther peut également tirer parti de la bibliothèque Goutte, qui est un intermédiaire entre les clients de test de Symfony et de Panther. Goutte envoie de vraies requêtes HTTP. Il est rapide et permet de naviguer sur n'importe quelle page web, et pas seulement sur celles de l'application testée. Il ne supporte pas JavaScript et d'autres fonctionnalités avancées car il est entièrement écrit en PHP.

L'intérêt de ces trois bibliothèques, c'est qu'elles implémentent exactement la même API. Vous pouvez donc passer de l'une à l'autre en appelant la méthode appropriée, et trouver le bon compromis pour chaque cas de test (Ai-je besoin de JavaScript ? Ai-je besoin d'authentifier vers un serveur SSO externe ? Ai-je envie d’accéder au noyau de la requête en cours ?).

<?php

use Symfony\Component\Panther\PantherTestCase;

class E2eTest extends PantherTestCase
{
    public function testMyApp()
    {
        $symfonyClient = static::createClient(); // A cute kitty: the Symfony's functional test too
        $goutteClient = static::createGoutteClient(); // An agile lynx: Goutte
        $pantherClient = static::createPantherClient(); // A majestic Panther
        
        // Both Goutte and Panther benefits from the built-in HTTP server
        
        // enjoy the same API for the 3 felines
        // $*client->request('GET', '...')

        $kernel = static::createKernel(); // You can also access to the app's kernel

        // ...
    }
}

Fonctionnalités

Contrairement aux bibliothèques de test et d’extraction de contenu, Panther :

  • Exécute le code JavaScript contenu dans les pages web
  • Supporte tout ce que Chrome ou Firefox implémente.
  • Permet la prise de captures d’écran
  • Peut attendre apparition d’éléments chargés de manière asynchrone.
  • Vous laisse exécuter votre propre code JS ou requêtes XPath dans le contexte de la page chargée.
  • Prend en charge les installations de serveur Selenium personnalisées.
  • Prend en charge les services de tests de navigateur à distance, y compris SauceLabs et BrowserStack.

Documentation

Puisque Panther implémente l’API de plusieurs bibliothèques populaires, elle a déjà une vaste documentation :

Variables d’environnement

Les variables d’environnement suivantes peuvent être définies pour modifier certains comportements de Panther :

  • PANTHER_NO_HEADLESS : permet de désactiver le mode “sans interface graphique ” des navigateurs (cela désactivera la fenêtre de tests, ce qui peut être utile pour déboger)
  • PANTHER_NO_SANDBOX : permet de désactiver le sandboxing de Chrome (dangereux, mais permet d’utiliser Panther dans des conteneurs)
  • PANTHER_WEB_SERVER_DIR : permet de changer la racine du document du projet (placée dans public/ par défaut)
  • PANTHER_CHROME_DRIVER_BINARY : permet d’utiliser un autre binaire chromedriver, au lieu de compter sur ceux déjà fournis par Panther

Intégration de Docker

Voici une image Docker minimale qui peut exécuter Panther :


FROM php:latest

RUN apt-get update && apt-get install -y zlib1g-dev chromium && docker-php-ext-install zip
ENV PANTHER_NO_SANDBOX 1

Faites-le avec la commande suivante : docker build . -t myproject

Exécutez la avec docker run -it -v "$PWD":/srv/myproject -w /srv/myproject myproject bin/phpunit

Intégration de Travis

Panther fonctionnera tout de suite avec Travis si vous ajoutez l’addon Chrome :


language: php
addons:
  chrome: stable

php:
  - 7.2

script:
  - phpunit

Intégration de AppVeyor

Panther fonctionnera immédiatement avec AppVeyor tant que Google Chrome est installé. Voici un fichier appveyor.yml pour exécuter vos tests avec Panther : 


build: false
platform: x86
clone_folder: c:\projects\myproject

cache:
  - '%LOCALAPPDATA%\Composer\files'

install:
  - ps: Set-Service wuauserv -StartupType Manual
  - cinst -y php composer googlechrome
  - refreshenv
  - cd c:\tools\php72
  - copy php.ini-production php.ini /Y
  - echo date.timezone="UTC" >> php.ini
  - echo extension_dir=ext >> php.ini
  - echo extension=php_openssl.dll >> php.ini
  - echo extension=php_mbstring.dll >> php.ini
  - echo extension=php_curl.dll >> php.ini
  - echo memory_limit=3G >> php.ini
  - cd %APPVEYOR_BUILD_FOLDER%
  - composer install --no-interaction --no-progress

test_script:
  - cd %APPVEYOR_BUILD_FOLDER%
  - php vendor\phpunit\phpunit\phpunit

Limitations

Les fonctionnalités suivantes ne sont pas encore supportées :

  • L’exploration de documents XML (seul le HTML est pris en charge)
  • Mise à jour de documents existants (les navigateurs sont principalement utilisés pour consommer des données, pas pour créer des pages web)
  • Définition de valeurs de formulaires à l’aide de la syntaxe de tableau PHP multidimensionnelle
  • Méthodes renvoyant vers une instance de \DOMElement (parce que cette bibliothèque utilise WebDriverElement en interne)
  • Sélection de choix invalides dans select

Et vous, que pensez-vous de Panther ? N’hésitez pas à contribuer à ce projet en faisant quelques PR sur les améliorations restantes !