Una biblioteca de pruebas de navegador y raspado web para PHP y Symfony
Panther es una conveniente biblioteca independiente para raspar sitios web y ejecutar pruebas de extremo a extremo utilizando navegadores reales .
Panther es súper poderosa. Aprovecha el protocolo web de W3C para impulsar los navegadores web nativos como Google Chrome y Firefox.
Panther es muy fácil de usar, porque implementa las populares API de navegación y domcrawler de Symfony, y contiene todas las características que necesita para probar sus aplicaciones. Suena familiar si alguna vez ha creado una prueba funcional para una aplicación Symfony: ¡ya que la API es exactamente la misma! Tenga en cuenta que Panther se puede usar en cada proyecto PHP, ya que es una biblioteca independiente.
Panther encuentra automáticamente su instalación local de Chrome o Firefox y los lanza, por lo que no necesita instalar nada más en su computadora, ¡no se necesita un servidor de Selenium!
En el modo de prueba, Panther inicia automáticamente su aplicación utilizando el servidor web PHP incorporado. Puede concentrarse en escribir sus pruebas o el escenario de raspado web y Panther se encargará de todo lo demás.
A diferencia de las pruebas y las bibliotecas de raspado web a las que está acostumbrado, Panther:
Use el compositor para instalar Panther en su proyecto. Es posible que desee utilizar el indicador --dev
si desea usar Panther solo para probar y no para rasparse web en un entorno de producción:
composer req symfony/panther
composer req --dev symfony/panther
Panther utiliza el protocolo WebDriver para controlar el navegador utilizado para rastrear sitios web.
En todos los sistemas, puede usar dbrekelmans/browser-driver-installer
para instalar ChromedRiver y Geckodriver localmente:
composer require --dev dbrekelmans/bdi
vendor/bin/bdi detect drivers
Panther detectará y usará controladores automáticamente almacenados en los drivers/
directorio.
Alternativamente, puede usar el Administrador de paquetes de su sistema operativo para instalarlos.
En Ubuntu, corre:
apt-get install chromium-chromedriver firefox-geckodriver
En Mac, usando Homebrew:
brew install chromedriver geckodriver
En ventanas, usando chocolate:
choco install chromedriver selenium-gecko-driver
Finalmente, puede descargar Manual ChromedRiver (para Chromium o Chrome) y Geckodriver (para Firefox) y ponerlos en cualquier lugar en su PATH
o en los drivers/
directorio de su proyecto.
Si tiene la intención de usar Panther para probar su aplicación, le recomendamos registrar la extensión de Panther Phpunit. Si bien no es estrictamente obligatoria, esta extensión mejora drásticamente la experiencia de prueba al aumentar el rendimiento y permitir usar el modo de depuración interactiva.
Cuando se usa la extensión junto con la variable de entorno PANTHER_ERROR_SCREENSHOT_DIR
, las pruebas que usan el cliente Panther que fallan o el error (después de crear el cliente) automáticamente obtendrá una captura de pantalla para ayudar a la depuración.
Para registrar la extensión de Panther, agregue las siguientes líneas a phpunit.xml.dist
:
<!-- phpunit.xml.dist -->
< extensions >
< extension class = " SymfonyComponentPantherServerExtension " />
</ extensions >
Sin la extensión, el servidor web utilizado por Panther para servir la aplicación en prueba se inicia a pedido y se detiene cuando se llama a tearDownAfterClass()
. Por otro lado, cuando se registra la extensión, el servidor web se detendrá solo después de la última prueba.
<?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 clase PantherTestCase
le permite escribir fácilmente pruebas E2E. Inicia automáticamente su aplicación utilizando el servidor web PHP incorporado y le permite gatearla con Panther. Para proporcionar todas las herramientas de prueba a las que está acostumbrado, extiende TestCase
de PhPunit.
Si está probando una aplicación Symfony, PantherTestCase
extiende automáticamente la clase WebTestCase
. Significa que puede crear fácilmente pruebas funcionales, que pueden ejecutar directamente el núcleo de su aplicación y acceder a todos sus servicios existentes. En este caso, puede usar todas las afirmaciones de prueba de rastreadores proporcionadas por Symfony con 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
}
}
Para ejecutar esta prueba:
bin/phpunit tests/E2eTest.php
Panther también le brinda acceso instantáneo a otras implementaciones basadas en Browserkit de Client
y Crawler
. A diferencia del cliente nativo de Panther, estos clientes alternativos no admiten la captura de JavaScript, CSS y capturas de pantalla, ¡pero son súper rápidos !
Hay dos clientes alternativos disponibles:
WebTestCase
. Es el cliente más rápido disponible, pero solo está disponible para aplicaciones Symfony.La parte divertida es que los 3 clientes implementan exactamente la misma API, por lo que puede cambiar de una a otra simplemente llamando al método de fábrica apropiado, lo que resulta en una buena compensación para cada caso de prueba (¿necesito JavaScript? ¿Necesita autenticar con un servidor SSO externo?
Aquí le mostramos cómo recuperar instancias de estos clientes:
<?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 proporciona una forma conveniente de probar aplicaciones con capacidades en tiempo real que utilizan Mercure, WebSocket y tecnologías similares.
PantherTestCase::createAdditionalPantherClient()
crea navegadores aislados adicionales que pueden interactuar entre sí. Por ejemplo, esto puede ser útil para probar una aplicación de chat que tiene varios usuarios conectados simultáneamente:
<?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 es necesario, puede usar Panther para acceder al contenido de la consola:
<?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 es necesario, puede configurar los argumentos para pasar al chromedriver
Binary:
<?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 ' , ' / ' );
}
}
Use el método Client::ping()
para verificar si la conexión WebDriver aún está activa (útil para tareas de larga duración).
Desde que Panther implementa la API de las bibliotecas populares, ya tiene una documentación extensa:
Client
, lea la documentación de BrowserkitCrawler
, lea la documentación de DomCrawlerLas siguientes variables de entorno se pueden configurar para cambiar el comportamiento de algunos Panther:
PANTHER_NO_HEADLESS
: para deshabilitar el modo sin cabeza del navegador (mostrará la ventana de prueba, útil para depurar)PANTHER_WEB_SERVER_DIR
: para cambiar la raíz del documento del proyecto (predeterminado a ./public/
, las rutas relativas deben comenzar por ./
)PANTHER_WEB_SERVER_PORT
: para cambiar el puerto del servidor web (predeterminado a 9080
)PANTHER_WEB_SERVER_ROUTER
: para usar un script de enrutador de servidor web que se ejecuta al inicio de cada solicitud HTTPPANTHER_EXTERNAL_BASE_URI
: para usar un servidor web externo (el servidor web de PHP incorporado no se iniciará)PANTHER_APP_ENV
: para anular la variable APP_ENV
pasada al servidor web que ejecuta la aplicación PHPPANTHER_ERROR_SCREENSHOT_DIR
: para establecer un directorio base para sus capturas de pantalla de falla/ ./var/error-screenshots
(por ejemplo,PANTHER_DEVTOOLS
: para alternar las herramientas de desarrollo del navegador ( enabled
por defecto, útil para depurar)PANTHER_ERROR_SCREENSHOT_ATTACH
: para agregar capturas de pantalla mencionadas anteriormente para probar la salida en formato de archivo adjunto JUnit Si desea cambiar el host y/o el puerto utilizado por el servidor web incorporado, pase el hostname
y port
al parámetro $options
del método createPantherClient()
:
// ...
$ client = self :: createPantherClient ([
' hostname ' => ' example.com ' , // Defaults to 127.0.0.1
' port ' => 8080 , // Defaults to 9080
]);
PANTHER_NO_SANDBOX
: para deshabilitar el sandboxing de Chrome (inseguro, pero permite usar pantera en contenedores)PANTHER_CHROME_ARGUMENTS
: para personalizar los argumentos de Chrome. Debe establecer PANTHER_NO_HEADLESS
para personalizar completamente.PANTHER_CHROME_BINARY
: para usar otro binario google-chrome
PANTHER_FIREFOX_ARGUMENTS
: para personalizar los argumentos de Firefox. Debe establecer PANTHER_NO_HEADLESS
para personalizar completamente.PANTHER_FIREFOX_BINARY
: para usar otro binario firefox
Según la especificación, las implementaciones de WebDriver devuelven solo el texto que se muestra de forma predeterminada. Cuando se filtra en una etiqueta head
(como title
), el método text()
devuelve una cadena vacía. Use el método html()
para obtener el contenido completo de la etiqueta, incluida la etiqueta en sí.
Panther puede hacer una pausa en sus suites de prueba después de una falla. Es un tiempo de descanso realmente apreciado por investigar el problema a través del navegador web. Para habilitar este modo, necesita la opción --debug
PHPUNIT sin el modo sin cabeza:
$ PANTHER_NO_HEADLESS=1 bin/phpunit --debug
Test 'AppAdminTest::testLogin' started
Error: something is wrong.
Press enter to continue...
Para usar el modo interactivo, la extensión PHPUNIT debe estar registrada.
A veces, es conveniente reutilizar una configuración de servidor web existente en lugar de iniciar el PHP incorporado. Para hacerlo, establezca la opción 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
}
}
Sucede que su aplicación PHP/Symfony podría servir varios nombres de dominio diferentes.
A medida que Panther guarde al cliente en la memoria entre las pruebas para mejorar el rendimiento, deberá ejecutar sus pruebas en procesos separados si escribe varias pruebas usando Panther para diferentes nombres de dominio.
Para hacerlo, puede usar la anotación nativa de PhPunit @runInSeparateProcess
.
ℹ NOTA: es realmente conveniente usar la opción external_base_uri
e iniciar su propio servidor web en segundo plano, porque Panther no tendrá que iniciar y detener su servidor en cada prueba. Symfony CLI puede ser una forma rápida y fácil de hacerlo.
Aquí hay un ejemplo que usa la opción external_base_uri
para determinar el nombre de dominio utilizado por el cliente:
<?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
}
}
Para usar un servidor proxy, establezca la siguiente variable de entorno: PANTHER_CHROME_ARGUMENTS='--proxy-server=socks://127.0.0.1:9050'
Para obligar a Chrome a aceptar certificados no válidos y autofirmados, establezca la siguiente variable de entorno: PANTHER_CHROME_ARGUMENTS='--ignore-certificate-errors'
Esta opción es insegura , use solo para probar en entornos de desarrollo, nunca en producción (por ejemplo, para web rastreadores).
Para Firefox, instanciar al cliente así:
$ client = Client :: createFirefoxClient ( null , null , [ ' capabilities ' => [ ' acceptInsecureCerts ' => true ]]);
Aquí hay una imagen mínima de Docker que puede ejecutar Panther con Chrome y 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
Construyelo con docker build . -t myproject
Ejecutarlo con docker run -it -v "$PWD":/srv/myproject -w /srv/myproject myproject bin/phpunit
Panther trabaja fuera de la caja con acciones de GitHub. Aquí hay un archivo mínimo .github/workflows/panther.yml
para ejecutar pruebas de pantera:
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 funcionará fuera de la caja con Travis CI si agrega el complemento Chrome. Aquí hay un archivo mínimo .travis.yml
para ejecutar pruebas de pantera:
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
Aquí hay un archivo mínimo .gitlab-ci.yml
para ejecutar pruebas de pantera con 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 funcionará fuera de la caja con Appveyor siempre que Google Chrome esté instalado. Aquí hay un archivo mínimo appveyor.yml
para ejecutar pruebas de pantera:
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 desea usar Panther con otras herramientas de prueba como LiipFunctionAltestBundle o si solo necesita usar una clase base diferente, Panther lo tiene cubierto. Le proporciona el SymfonyComponentPantherPantherTestCaseTrait
y puede usarlo para mejorar su infraestructura de prueba existente con cierta paja genial:
<?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 ' , ' / ' );
}
}
Actualmente no son compatibles con las siguientes características:
DOMElement
(porque esta biblioteca usa WebDriverElement
internamente)¡Las solicitudes de extracción son bienvenidas para llenar los vacíos restantes!
Si está utilizando Bootstrap 5, puede tener un problema con las pruebas. Bootstrap 5 implementa un efecto de desplazamiento, que tiende a engañar a Panther.
Para solucionar esto, le aconsejamos que desactive este efecto configurando la variable Bootstrap 5 $ Enable-Smooth-Scroll a False en su archivo de estilo.
$enable-smooth-scroll : false;
Muchas de las especies de gatos salvajes están muy amenazadas. Si le gusta este software, ayude a guardar las Panteras (reales) donando a la organización Panthera.
Creado por Kévin Dunglas. Patrocinado por Les-Tilleuls.coop.
Panther está construido sobre PHP WebDriver y varias otras bibliotecas de Foss. Se ha inspirado en NightWatch.js, una herramienta de prueba basada en WebDriver para JavaScript.