PHP和Symfony的浏览器测试和网络刮擦库
Panther是一个方便的独立库,用于刮擦网站并使用真实浏览器运行端到端测试。
Panther超级强大。它利用W3C的Web驱动器协议来驱动本机Web浏览器,例如Google Chrome和Firefox。
Panther非常易于使用,因为它实现了Symfony流行的Browserkit和Domcrawler API,并且包含您测试应用程序所需的所有功能。如果您曾经为Symfony应用程序创建了功能测试,这听起来很熟悉:因为API完全相同!请记住,Panther可以在每个PHP项目中使用,因为它是一个独立的库。
Panther会自动找到您的本地安装Chrome或Firefox并启动它们,因此您无需在计算机上安装其他任何内容,不需要硒服务器!
在测试模式下,Panther会使用PHP内置的Web-Server自动启动您的应用程序。您可以专注于编写测试或网络剪裁方案,而Panther将负责其他所有内容。
与测试和网络刮擦库不同,Panther:
使用作曲家在项目中安装Panther。如果要使用Panther仅测试而不是在生产环境中进行网络刮擦,则可能需要使用--dev
标志:
composer req symfony/panther
composer req --dev symfony/panther
Panther使用WebDriver协议来控制用于爬网网站的浏览器。
在所有系统上,您都可以使用dbrekelmans/browser-driver-installer
在本地安装Chromedriver和Geckodriver:
composer require --dev dbrekelmans/bdi
vendor/bin/bdi detect drivers
Panther将检测并使用drivers/
目录中存储的驱动程序。
另外,您可以使用操作系统的软件包管理器安装它们。
在Ubuntu上,运行:
apt-get install chromium-chromedriver firefox-geckodriver
在Mac上使用自制:
brew install chromedriver geckodriver
在窗户上,使用巧克力:
choco install chromedriver selenium-gecko-driver
最后,您可以下载手动Chromedriver(用于Chromium或Chrome)和Geckodriver(对于Firefox),并将它们放入您的PATH
或项目的drivers/
目录中。
如果您打算使用Panther测试您的应用程序,我们强烈建议注册Panther Phpunit扩展程序。尽管不是严格强制性的,但这种扩展可以通过提高性能并允许使用交互式调试模式来大大改善测试经验。
与PANTHER_ERROR_SCREENSHOT_DIR
环境变量一起使用扩展程序时,使用Panther客户端的测试失败或错误(创建客户端后)将自动获取屏幕截图以帮助调试。
要注册Panther Extension,请将以下行添加到phpunit.xml.dist
:
<!-- phpunit.xml.dist -->
< extensions >
< extension class = " SymfonyComponentPantherServerExtension " />
</ extensions >
如果没有扩展名,Panther使用的Web服务器就可以按需开始测试,并在调用tearDownAfterClass()
时停止。另一方面,当注册扩展程序时,仅在最后一次测试之后才会停止Web服务器。
<?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!
PantherTestCase
类允许您轻松编写E2E测试。它会使用内置的PHP Web服务器自动启动您的应用程序,并让您使用Panther爬网。为了提供您使用的所有测试工具,它扩展了Phpunit的TestCase
。
如果要测试Symfony应用程序, PantherTestCase
会自动扩展WebTestCase
类。这意味着您可以轻松创建功能测试,该测试可以直接执行应用程序的内核并访问所有现有服务。在这种情况下,您可以使用Symfony与Panther提供的所有Crawler测试断言。
<?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
}
}
进行此测试:
bin/phpunit tests/E2eTest.php
Panther还使您可以立即访问Client
和Crawler
的其他基于浏览器的实现。与Panther的本地客户不同,这些替代客户不支持JavaScript,CSS和屏幕截图捕获,但它们非常快!
有两个替代客户可用:
WebTestCase
提供的符号内核。它是最快的客户端,但仅适用于Symfony应用程序。有趣的部分是3个客户实现了完全相同的API,因此您可以通过调用适当的工厂方法来从一个端子转到另一个API,从而为每个测试用例提供了良好的权衡(我需要JavaScript吗?我是否?需要使用外部SSO服务器进行身份验证吗?
这是如何检索这些客户的实例:
<?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提供了一种使用Mercure,Websocket和类似技术的实时功能测试应用程序的方便方法。
PantherTestCase::createAdditionalPantherClient()
创建了可以相互交互的其他隔离浏览器。例如,这对于测试与几个用户同时连接的聊天应用程序非常有用:
<?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 ? ' );
}
}
如果需要,您可以使用Panther访问控制台的内容:
<?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
}
}
如果需要,您可以配置参数以传递到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 ' , ' / ' );
}
}
使用Client::ping()
方法检查WebDriver连接是否仍处于活动状态(对长期运行的任务有用)。
由于Panther实现了流行库的API,因此它已经有一个广泛的文档:
Client
类,请阅读browserkit文档Crawler
类,请阅读Domcrawler文档可以设置以下环境变量来改变某些豹的行为:
PANTHER_NO_HEADLESS
:禁用浏览器的无头模式(将显示测试窗口,可用于调试)PANTHER_WEB_SERVER_DIR
:要更改项目的文档root(默认为./public/
,相对路径必须从./
开始)PANTHER_WEB_SERVER_PORT
:更改Web服务器的端口(默认为9080
)PANTHER_WEB_SERVER_ROUTER
:使用在每个HTTP请求开始时运行的Web服务器路由器脚本PANTHER_EXTERNAL_BASE_URI
:使用外部Web服务器(将不会启动PHP内置的Web服务器)PANTHER_APP_ENV
:覆盖APP_ENV
变量传递给运行PHP应用程序的Web服务器PANTHER_ERROR_SCREENSHOT_DIR
:为失败/错误屏幕./var/error-screenshots
设置基本目录(例如PANTHER_DEVTOOLS
:要切换浏览器的开发工具(默认enabled
,可用于调试)PANTHER_ERROR_SCREENSHOT_ATTACH
:添加上面提到的屏幕截图以测试junit附件格式的输出如果要更改内置Web服务器使用的主机和/或端口,请将hostname
和port
传递给createPantherClient()
方法的$options
参数:
// ...
$ client = self :: createPantherClient ([
' hostname ' => ' example.com ' , // Defaults to 127.0.0.1
' port ' => 8080 , // Defaults to 9080
]);
PANTHER_NO_SANDBOX
:禁用Chrome的沙箱(不安全,但允许在容器中使用Panther)PANTHER_CHROME_ARGUMENTS
:自定义Chrome参数。您需要将PANTHER_NO_HEADLESS
设置为完全自定义。PANTHER_CHROME_BINARY
:使用另一个google-chrome
二进制PANTHER_FIREFOX_ARGUMENTS
:自定义firefox参数。您需要将PANTHER_NO_HEADLESS
设置为完全自定义。PANTHER_FIREFOX_BINARY
:使用另一个firefox
二进制根据规格,WebDriver实现默认情况下仅返回显示的文本。当您在head
Tag上过滤(如title
)时,方法text()
返回一个空字符串。使用方法html()
获取标签的完整内容,包括标签本身。
失败后,Panther可以在测试套房中停下来。通过Web浏览器调查问题,这是一个休息时间。为了启用此模式,您需要没有无头模式的--debug
phpunit选项:
$ PANTHER_NO_HEADLESS=1 bin/phpunit --debug
Test 'AppAdminTest::testLogin' started
Error: something is wrong.
Press enter to continue...
要使用交互式模式,必须注册Phpunit扩展名。
有时,重复使用现有的Web服务器配置而不是启动内置PHP ONE是很方便的。为此,设置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
}
}
碰巧您的PHP/Symfony应用程序可能会提供多个不同的域名。
当Panther将客户端保存在测试之间以提高性能的内存中,如果使用Panther为不同的域名编写多个测试,则必须在单独的过程中运行测试。
为此,您可以使用本机@runInSeparateProcess
phpunit注释。
ℹ注意:使用external_base_uri
选项并在后台启动自己的Web服务器确实很方便,因为Panther不必在每个测试中启动和停止服务器。 Symfony CLI可以是一种快速简便的方法。
这是使用external_base_uri
选项确定客户端使用的域名的示例:
<?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
}
}
要使用代理服务器,请设置以下环境变量: PANTHER_CHROME_ARGUMENTS='--proxy-server=socks://127.0.0.1:9050'
要迫使Chrome接受无效和自签名的证书,请设置以下环境变量: PANTHER_CHROME_ARGUMENTS='--ignore-certificate-errors'
此选项是不安全的,仅在开发环境中进行测试,从不在生产中(例如爬虫)。
对于Firefox,这样就可以实例化客户:
$ client = Client :: createFirefoxClient ( null , null , [ ' capabilities ' => [ ' acceptInsecureCerts ' => true ]]);
这是一个最小的Docker图像,可以与Chrome和Firefox一起运行Panther:
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
使用docker build . -t myproject
使用docker run -it -v "$PWD":/srv/myproject -w /srv/myproject myproject bin/phpunit
Panther用GitHub动作开箱即用。这是一个最小的.github/workflows/panther.yml
文件,用于运行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
如果您添加Chrome插件,Panther将使用Travis CI开箱即用。这是一个最小的.travis.yml
文件,用于运行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
这是一个最小的.gitlab-ci.yml
文件,用于使用Gitlab CI运行Panther测试:
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
只要安装Google Chrome,Panther就会与Appveyor开箱即用。这是一个最小的appveyor.yml
文件,用于运行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
如果您想将Panther用于其他测试工具,例如LiipfunctionalTestBundle,或者只需使用其他基类,Panther就可以覆盖您。它为您提供SymfonyComponentPantherPantherTestCaseTrait
,您可以使用它来增强您的现有测试基础结构,并具有某些豹:
<?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 ' , ' / ' );
}
}
当前不支持以下功能:
DOMElement
实例的方法(因为此库内部使用WebDriverElement
)欢迎拉动请求填补剩余的空白!
如果您使用的是Bootstrap 5,则可能在测试中遇到问题。 Bootstrap 5实现了滚动效果,这往往会误导Panther。
为了解决此问题,我们建议您通过将Bootstrap 5 $ enable-smooth-scroll变量设置为“ false”中的false,以在样式文件中停用这种效果。
$enable-smooth-scroll : false;
许多野猫物种受到高度威胁。如果您喜欢此软件,请通过向Panthera组织捐款来帮助保存(真实的)黑豹。
由KévinDunglas创建。由les-tilleuls.coop赞助。
Panther建在PHP Webdriver和其他几个FOSS库的顶部。它的启发是由JavaScript的基于网络驱动器的测试工具NightWatch.js的启发。