Une bibliothèque de tests de navigateur et de grattage Web pour PHP et Symfony
Panther est une bibliothèque autonome pratique pour gratter les sites Web et exécuter des tests de bout en bout en utilisant de vrais navigateurs .
Panther est super puissant. Il exploite le protocole WebDriver du W3C pour piloter des navigateurs Web natifs tels que Google Chrome et Firefox.
Panther est très facile à utiliser, car il implémente les API populaires Browserkit et Domcrawler de Symfony, et contient toutes les fonctionnalités dont vous avez besoin pour tester vos applications. Cela semblera familier si vous avez déjà créé un test fonctionnel pour une application Symfony: car l'API est exactement la même! Gardez à l'esprit que Panther peut être utilisé dans chaque projet PHP, car il s'agit d'une bibliothèque autonome.
Panther trouve automatiquement votre installation locale de Chrome ou Firefox et les lance, vous n'avez donc pas besoin d'installer autre chose sur votre ordinateur, un serveur Selenium n'est pas nécessaire!
En mode test, Panther démarre automatiquement votre application à l'aide du serveur Web intégré PHP. Vous pouvez vous concentrer sur la rédaction de vos tests ou de votre scénario de crampon sur le Web et Panther s'occupera de tout le reste.
Contrairement aux bibliothèques de tests et de grattage Web auxquelles vous êtes habitué, Panther:
Utilisez Composer pour installer Panther dans votre projet. Vous souhaiterez peut-être utiliser l'indicateur --dev
si vous souhaitez utiliser Panther pour les tests uniquement et non pour le grattage Web dans un environnement de production:
composer req symfony/panther
composer req --dev symfony/panther
Panther utilise le protocole WebDriver pour contrôler le navigateur utilisé pour faire des sites Web.
Sur tous les systèmes, vous pouvez utiliser dbrekelmans/browser-driver-installer
pour installer Chromedriver et Geckodriver localement:
composer require --dev dbrekelmans/bdi
vendor/bin/bdi detect drivers
Panther détectera et utilisera automatiquement les pilotes stockés dans les drivers/
répertoires.
Alternativement, vous pouvez utiliser le gestionnaire de packages de votre système d'exploitation pour les installer.
Sur Ubuntu, courez:
apt-get install chromium-chromedriver firefox-geckodriver
Sur Mac, en utilisant Homebrew:
brew install chromedriver geckodriver
Sur les fenêtres, en utilisant le chocolat:
choco install chromedriver selenium-gecko-driver
Enfin, vous pouvez télécharger manuellement Chromedriver (pour le chrome ou le chrome) et Geckodriver (pour Firefox) et les mettre n'importe où sur votre PATH
ou dans les drivers/
répertoire de votre projet.
Si vous avez l'intention d'utiliser Panther pour tester votre application, nous vous recommandons fortement d'enregistrer l'extension Panther Phpunit. Bien qu'il ne soit pas strictement obligatoire, cette extension améliore considérablement l'expérience de test en augmentant les performances et en permettant d'utiliser le mode de débogage interactif.
Lorsque vous utilisez l'extension conjointement avec la variable d'environnement PANTHER_ERROR_SCREENSHOT_DIR
, les tests à l'aide du client Panther qui échouent ou l'erreur (après la création du client) obtiendra automatiquement une capture d'écran prise pour aider à déboguer.
Pour enregistrer l'extension Panther, ajoutez les lignes suivantes à phpunit.xml.dist
:
<!-- phpunit.xml.dist -->
< extensions >
< extension class = " SymfonyComponentPantherServerExtension " />
</ extensions >
Sans l'extension, le serveur Web utilisé par Panther pour servir l'application testée est démarré à la demande et arrêté lorsque tearDownAfterClass()
est appelé. D'un autre côté, lorsque l'extension est enregistrée, le serveur Web ne sera arrêté qu'après le tout dernier test.
<?php
use Symfony Component Panther Client ;
require __DIR__ . ' /vendor/autoload.php ' ; // Composer's autoloader
$ client = Client :: createChromeClient ();
// Or, if you care about the open web and prefer to use Firefox
$ client = Client :: createFirefoxClient ();
$ client -> request ( ' GET ' , ' https://api-platform.com ' ); // Yes, this website is 100% written in JavaScript
$ client -> clickLink ( ' Getting started ' );
// Wait for an element to be present in the DOM (even if hidden)
$ crawler = $ client -> waitFor ( ' #installing-the-framework ' );
// Alternatively, wait for an element to be visible
$ crawler = $ client -> waitForVisibility ( ' #installing-the-framework ' );
echo $ crawler -> filter ( ' #installing-the-framework ' )-> text ();
$ client -> takeScreenshot ( ' screen.png ' ); // Yeah, screenshot!
La classe PantherTestCase
vous permet d'écrire facilement les tests E2E. Il démarre automatiquement votre application à l'aide du serveur Web PHP intégré et vous permet de le ramper à l'aide de Panther. Pour fournir tous les outils de test auxquels vous êtes habitué, il étend TestCase
de Phpunit.
Si vous testez 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. Dans ce cas, vous pouvez utiliser toutes les affirmations de test de robots fournies fournies par Symfony avec Panther.
<?php
namespace App Tests ;
use Symfony Component Panther PantherTestCase ;
class E2eTest extends PantherTestCase
{
public function testMyApp (): void
{
$ client = static :: createPantherClient (); // Your app is automatically started using the built-in web server
$ client -> request ( ' GET ' , ' /mypage ' );
// Use any PHPUnit assertion, including the ones provided by Symfony
$ this -> assertPageTitleContains ( ' My Title ' );
$ this -> assertSelectorTextContains ( ' #main ' , ' My body ' );
// Or the one provided by Panther
$ this -> assertSelectorIsEnabled ( ' .search ' );
$ this -> assertSelectorIsDisabled ( ' [type="submit"] ' );
$ this -> assertSelectorIsVisible ( ' .errors ' );
$ this -> assertSelectorIsNotVisible ( ' .loading ' );
$ this -> assertSelectorAttributeContains ( ' .price ' , ' data-old-price ' , ' 42 ' );
$ this -> assertSelectorAttributeNotContains ( ' .price ' , ' data-old-price ' , ' 36 ' );
// Use waitForX methods to wait until some asynchronous process finish
$ client -> waitFor ( ' .popin ' ); // wait for element to be attached to the DOM
$ client -> waitForStaleness ( ' .popin ' ); // wait for element to be removed from the DOM
$ client -> waitForVisibility ( ' .loader ' ); // wait for element of the DOM to become visible
$ client -> waitForInvisibility ( ' .loader ' ); // wait for element of the DOM to become hidden
$ client -> waitForElementToContain ( ' .total ' , ' 25 € ' ); // wait for text to be inserted in the element content
$ client -> waitForElementToNotContain ( ' .promotion ' , ' 5% ' ); // wait for text to be removed from the element content
$ client -> waitForEnabled ( ' [type="submit"] ' ); // wait for the button to become enabled
$ client -> waitForDisabled ( ' [type="submit"] ' ); // wait for the button to become disabled
$ client -> waitForAttributeToContain ( ' .price ' , ' data-old-price ' , ' 25 € ' ); // wait for the attribute to contain content
$ client -> waitForAttributeToNotContain ( ' .price ' , ' data-old-price ' , ' 25 € ' ); // wait for the attribute to not contain content
// Let's predict the future
$ this -> assertSelectorWillExist ( ' .popin ' ); // element will be attached to the DOM
$ this -> assertSelectorWillNotExist ( ' .popin ' ); // element will be removed from the DOM
$ this -> assertSelectorWillBeVisible ( ' .loader ' ); // element will be visible
$ this -> assertSelectorWillNotBeVisible ( ' .loader ' ); // element will not be visible
$ this -> assertSelectorWillContain ( ' .total ' , ' €25 ' ); // text will be inserted in the element content
$ this -> assertSelectorWillNotContain ( ' .promotion ' , ' 5% ' ); // text will be removed from the element content
$ this -> assertSelectorWillBeEnabled ( ' [type="submit"] ' ); // button will be enabled
$ this -> assertSelectorWillBeDisabled ( ' [type="submit"] ' ); // button will be disabled
$ this -> assertSelectorAttributeWillContain ( ' .price ' , ' data-old-price ' , ' €25 ' ); // attribute will contain content
$ this -> assertSelectorAttributeWillNotContain ( ' .price ' , ' data-old-price ' , ' €25 ' ); // attribute will not contain content
}
}
Pour exécuter ce test:
bin/phpunit tests/E2eTest.php
Panther vous donne également un accès instantané à d'autres implémentations basées sur Browserkit de Client
et Crawler
. Contrairement au client natif de Panther, ces clients alternatifs ne prennent pas en charge JavaScript, CSS et capture d'écran, mais ils sont super rapides !
Deux clients alternatifs sont disponibles:
WebTestCase
. C'est le client le plus rapide disponible, mais il n'est disponible que pour les applications Symfony.La partie amusante est que les 3 clients implémentent exactement la même API, afin que vous puissiez passer de l'un à l'autre simplement en appelant la méthode d'usine appropriée, résultant en un bon compromis pour chaque cas de test (ai-je besoin de JavaScript? Est-ce que je suis Besoin de s'authentifier avec un serveur SSO externe?
Voici comment récupérer les instances de ces clients:
<?php
namespace App Tests ;
use Symfony Component Panther PantherTestCase ;
use Symfony Component Panther Client ;
class E2eTest extends PantherTestCase
{
public function testMyApp (): void
{
$ symfonyClient = static :: createClient (); // A cute kitty: Symfony's functional test tool
$ httpBrowserClient = static :: createHttpBrowserClient (); // An agile lynx: HttpBrowser
$ pantherClient = static :: createPantherClient (); // A majestic Panther
$ firefoxClient = static :: createPantherClient ([ ' browser ' => static :: FIREFOX ]); // A splendid Firefox
// Both HttpBrowser and Panther benefits from the built-in HTTP server
$ customChromeClient = Client :: createChromeClient ( null , null , [], ' https://example.com ' ); // Create a custom Chrome client
$ customFirefoxClient = Client :: createFirefoxClient ( null , null , [], ' https://example.com ' ); // Create a custom Firefox client
$ customSeleniumClient = Client :: createSeleniumClient ( ' http://127.0.0.1:4444/wd/hub ' , null , ' https://example.com ' ); // Create a custom Selenium client
// When initializing a custom client, the integrated web server IS NOT started automatically.
// Use PantherTestCase::startWebServer() or WebServerManager if you want to start it manually.
// enjoy the same API for the 3 felines
// $*client->request('GET', '...')
$ kernel = static :: createKernel (); // If you are testing a Symfony app, you also have access to the kernel
// ...
}
}
Panther fournit un moyen pratique de tester les applications avec des capacités en temps réel qui utilisent Mercure, WebSocket et des technologies similaires.
PantherTestCase::createAdditionalPantherClient()
crée des navigateurs isolés supplémentaires qui peuvent interagir les uns avec les autres. Par exemple, cela peut être utile pour tester une application de chat avec plusieurs utilisateurs connectés simultanément:
<?php
use Symfony Component Panther PantherTestCase ;
class ChatTest extends PantherTestCase
{
public function testChat (): void
{
$ client1 = self :: createPantherClient ();
$ client1 -> request ( ' GET ' , ' /chat ' );
// Connect a 2nd user using an isolated browser and say hi!
$ client2 = self :: createAdditionalPantherClient ();
$ client2 -> request ( ' GET ' , ' /chat ' );
$ client2 -> submitForm ( ' Post message ' , [ ' message ' => ' Hi folks ? ' ]);
// Wait for the message to be received by the first client
$ client1 -> waitFor ( ' .message ' );
// Symfony Assertions are always executed in the **primary** browser
$ this -> assertSelectorTextContains ( ' .message ' , ' Hi folks ? ' );
}
}
Si nécessaire, vous pouvez utiliser Panther pour accéder au contenu de la console:
<?php
use Symfony Component Panther PantherTestCase ;
class ConsoleTest extends PantherTestCase
{
public function testConsole (): void
{
$ client = self :: createPantherClient (
[],
[],
[
' capabilities ' => [
' goog:loggingPrefs ' => [
' browser ' => ' ALL ' , // calls to console.* methods
' performance ' => ' ALL ' , // performance data
],
],
]
);
$ client -> request ( ' GET ' , ' / ' );
$ consoleLogs = $ client -> getWebDriver ()-> manage ()-> getLog ( ' browser ' ); // console logs
$ performanceLogs = $ client -> getWebDriver ()-> manage ()-> getLog ( ' performance ' ); // performance logs
}
}
Si nécessaire, vous pouvez configurer les arguments à passer au binaire chromedriver
:
<?php
use Symfony Component Panther PantherTestCase ;
class MyTest extends PantherTestCase
{
public function testLogging (): void
{
$ client = self :: createPantherClient (
[],
[],
[
' chromedriver_arguments ' => [
' --log-path=myfile.log ' ,
' --log-level=DEBUG '
],
]
);
$ client -> request ( ' GET ' , ' / ' );
}
}
Utilisez la méthode Client::ping()
pour vérifier si la connexion WebDriver est toujours active (utile pour les tâches de longue durée).
Depuis Panther met en œuvre l'API des bibliothèques populaires, il a déjà une documentation approfondie:
Client
, lisez la documentation BrowserKitCrawler
, lisez la documentation DomcrawlerLes variables d'environnement suivantes peuvent être définies pour modifier le comportement de certains Panther:
PANTHER_NO_HEADLESS
: pour désactiver le mode sans tête du navigateur (affichera la fenêtre de test, utile pour déboguer)PANTHER_WEB_SERVER_DIR
: Pour modifier la racine du document du projet (par défaut ./public/
, les chemins relatifs doivent commencer par ./
)PANTHER_WEB_SERVER_PORT
: pour modifier le port du serveur Web (par défaut à 9080
)PANTHER_WEB_SERVER_ROUTER
: Pour utiliser un script de routeur de serveur Web qui est exécuté au début de chaque demande HTTPPANTHER_EXTERNAL_BASE_URI
: Pour utiliser un serveur Web externe (le serveur Web intégré PHP ne sera pas démarré)PANTHER_APP_ENV
: Pour remplacer la variable APP_ENV
transmise au serveur Web exécutant l'application PHPPANTHER_ERROR_SCREENSHOT_DIR
: Pour définir un répertoire de base pour vos captures d'écran de défaillance / erreur (par exemple , ./var/error-screenshots
screenshots)PANTHER_DEVTOOLS
: pour basculer les outils de développement du navigateur (par défaut enabled
, utile pour déboguer)PANTHER_ERROR_SCREENSHOT_ATTACH
: Pour ajouter des captures d'écran mentionnées ci-dessus pour tester la sortie au format JUnit Pixe Si vous souhaitez modifier l'hôte et / ou le port utilisé par le serveur Web intégré, passez le hostname
et port
au paramètre $options
de la méthode createPantherClient()
:
// ...
$ client = self :: createPantherClient ([
' hostname ' => ' example.com ' , // Defaults to 127.0.0.1
' port ' => 8080 , // Defaults to 9080
]);
PANTHER_NO_SANDBOX
: pour désactiver le sable de chrome (dangereux, mais permet d'utiliser Panther dans les conteneurs)PANTHER_CHROME_ARGUMENTS
: pour personnaliser les arguments chromés. Vous devez définir PANTHER_NO_HEADLESS
pour personnaliser entièrement.PANTHER_CHROME_BINARY
: utiliser un autre binaire google-chrome
PANTHER_FIREFOX_ARGUMENTS
: pour personnaliser les arguments Firefox. Vous devez définir PANTHER_NO_HEADLESS
pour personnaliser entièrement.PANTHER_FIREFOX_BINARY
: utiliser un autre binaire firefox
Selon la spécification, les implémentations WebDriver ne renvoient que le texte affiché par défaut. Lorsque vous filtrez sur une balise head
(comme title
), la méthode text()
renvoie une chaîne vide. Utilisez la méthode html()
pour obtenir le contenu complet de la balise, y compris la balise elle-même.
Panther peut faire une pause dans vos suites de tests après une défaillance. C'est un temps de pause vraiment apprécié pour avoir enquêté sur le problème via le navigateur Web. Pour activer ce mode, vous avez besoin de l'option --debug
phpunit sans le mode sans tête:
$ PANTHER_NO_HEADLESS=1 bin/phpunit --debug
Test 'AppAdminTest::testLogin' started
Error: something is wrong.
Press enter to continue...
Pour utiliser le mode interactif, l'extension PHPUnit doit être enregistrée.
Parfois, il est pratique de réutiliser une configuration de serveur Web existant au lieu de démarrer la PHP intégrée. Pour ce faire, définissez l'option external_base_uri
:
<?php
namespace App Tests ;
use Symfony Component Panther PantherTestCase ;
class E2eTest extends PantherTestCase
{
public function testMyApp (): void
{
$ pantherClient = static :: createPantherClient ([ ' external_base_uri ' => ' https://localhost ' ]);
// the PHP integrated web server will not be started
}
}
Il arrive que votre application PHP / Symfony puisse servir plusieurs noms de domaine différents.
Alors que Panther enregistre le client en mémoire entre les tests pour améliorer les performances, vous devrez exécuter vos tests dans des processus distincts si vous écrivez plusieurs tests utilisant Panther pour différents noms de domaine.
Pour ce faire, vous pouvez utiliser l'annotation native @runInSeparateProcess
phpunit.
ℹ Remarque: il est vraiment pratique d'utiliser l'option external_base_uri
et de démarrer votre propre serveur Web en arrière-plan, car Panther n'aura pas à démarrer et à arrêter votre serveur à chaque test. Symfony CLI peut être un moyen rapide et facile de le faire.
Voici un exemple utilisant l'option external_base_uri
pour déterminer le nom de domaine utilisé par le client:
<?php
namespace App Tests ;
use Symfony Component Panther PantherTestCase ;
class FirstDomainTest extends PantherTestCase
{
/**
* @runInSeparateProcess
*/
public function testMyApp (): void
{
$ pantherClient = static :: createPantherClient ([
' external_base_uri ' => ' http://mydomain.localhost:8000 ' ,
]);
// Your tests
}
}
<?php
namespace App Tests ;
use Symfony Component Panther PantherTestCase ;
class SecondDomainTest extends PantherTestCase
{
/**
* @runInSeparateProcess
*/
public function testMyApp (): void
{
$ pantherClient = static :: createPantherClient ([
' external_base_uri ' => ' http://anotherdomain.localhost:8000 ' ,
]);
// Your tests
}
}
Pour utiliser un serveur proxy, définissez la variable d'environnement suivante: PANTHER_CHROME_ARGUMENTS='--proxy-server=socks://127.0.0.1:9050'
Pour forcer Chrome à accepter des certificats invalides et auto-signés, définissez la variable d'environnement suivante: PANTHER_CHROME_ARGUMENTS='--ignore-certificate-errors'
Cette option n'est pas sécurisée , utilisez-le uniquement pour tester dans les environnements de développement, jamais en production (par exemple pour le Web Crawlers).
Pour Firefox, instanciez le client comme ceci:
$ client = Client :: createFirefoxClient ( null , null , [ ' capabilities ' => [ ' acceptInsecureCerts ' => true ]]);
Voici une image Docker minimale qui peut exécuter Panther avec Chrome et Firefox:
FROM php:alpine
# Chromium and ChromeDriver
ENV PANTHER_NO_SANDBOX 1
# Not mandatory, but recommended
ENV PANTHER_CHROME_ARGUMENTS= '--disable-dev-shm-usage'
RUN apk add --no-cache chromium chromium-chromedriver
# Firefox and GeckoDriver (optional)
ARG GECKODRIVER_VERSION=0.28.0
RUN apk add --no-cache firefox libzip-dev;
docker-php-ext-install zip
RUN wget -q https://github.com/mozilla/geckodriver/releases/download/v$GECKODRIVER_VERSION/geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz;
tar -zxf geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz -C /usr/bin;
rm geckodriver-v$GECKODRIVER_VERSION-linux64.tar.gz
Construisez-le avec docker build . -t myproject
l'exécutez-le avec docker run -it -v "$PWD":/srv/myproject -w /srv/myproject myproject bin/phpunit
Panther travaille hors de la boîte avec des actions GitHub. Voici un fichier .github/workflows/panther.yml
minimal pour exécuter les tests Panther:
name : Run Panther tests
on : [ push, pull_request ]
jobs :
tests :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v2
- name : Install dependencies
run : composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name : Run test suite
run : bin/phpunit
Panther fonctionnera hors de la boîte avec Travis CI si vous ajoutez l'addon chrome. Voici un fichier .travis.yml
minimal pour exécuter les tests Panther:
language : php
addons :
# If you don't use Chrome, or Firefox, remove the corresponding line
chrome : stable
firefox : latest
php :
- 8.0
script :
- bin/phpunit
Voici un fichier .gitlab-ci.yml
minimal pour exécuter des tests Panther avec Gitlab CI:
image : ubuntu
before_script :
- apt-get update
- apt-get install software-properties-common -y
- ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
- apt-get install curl wget php php-cli php7.4 php7.4-common php7.4-curl php7.4-intl php7.4-xml php7.4-opcache php7.4-mbstring php7.4-zip libfontconfig1 fontconfig libxrender-dev libfreetype6 libxrender1 zlib1g-dev xvfb chromium-chromedriver firefox-geckodriver -y -qq
- export PANTHER_NO_SANDBOX=1
- export PANTHER_WEB_SERVER_PORT=9080
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
- php composer-setup.php --install-dir=/usr/local/bin --filename=composer
- php -r "unlink('composer-setup.php');"
- composer install
test :
script :
- bin/phpunit
Panther fonctionnera hors de la boîte avec Appveyor tant que Google Chrome sera installé. Voici un fichier minimal appveyor.yml
pour exécuter les tests Panther:
build : false
platform : x86
clone_folder : c:projectsmyproject
cache :
- ' %LOCALAPPDATA%Composerfiles '
install :
- ps : Set-Service wuauserv -StartupType Manual
- cinst -y php composer googlechrome chromedriver firfox selenium-gecko-driver
- refreshenv
- cd c:toolsphp80
- 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 binphpunit
Si vous souhaitez utiliser Panther avec d'autres outils de test comme LiipFonctionalTestBundle ou si vous avez juste besoin d'utiliser une classe de base différente, Panther vous a couvert. Il vous fournit le SymfonyComponentPantherPantherTestCaseTrait
et vous pouvez l'utiliser pour améliorer votre infrastructure de test existante avec une certaine Awesomeness:
<?php
namespace App Tests Controller ;
use Liip FunctionalTestBundle Test WebTestCase ;
use Symfony Component Panther PantherTestCaseTrait ;
class DefaultControllerTest extends WebTestCase
{
use PantherTestCaseTrait ; // this is the magic. Panther is now available.
public function testWithFixtures (): void
{
$ this -> loadFixtures ([]); // load your fixtures
$ client = self :: createPantherClient (); // create your panther client
$ client -> request ( ' GET ' , ' / ' );
}
}
Les fonctionnalités suivantes ne sont pas actuellement prises en charge:
DOMElement
(car cette bibliothèque utilise WebDriverElement
en interne)Les demandes de traction sont les bienvenues pour combler les lacunes restantes!
Si vous utilisez Bootstrap 5, vous pouvez avoir un problème avec les tests. Bootstrap 5 met en œuvre un effet de défilement, qui a tendance à induire la panthère en erreur.
Pour résoudre ce problème, nous vous conseillons de désactiver cet effet en définissant la variable Bootstrap 5 $ Activer-Smooth-Scroll sur FALSE dans votre fichier de style.
$enable-smooth-scroll : false;
De nombreuses espèces de chats sauvages sont très menacées. Si vous aimez ce logiciel, aidez à enregistrer les (vrais) Panthers en faisant un don à l'organisation Panthera.
Créé par Kévin Dunglas. Parrainé par Les-Tilleuls.coop.
Panther est construit au-dessus de PHP WebDriver et de plusieurs autres bibliothèques FOSS. Il a été inspiré par Nightwatch.js, un outil de test basé sur WebDriver pour JavaScript.