跳到内容

端到端测试

编辑此页

Panther 组件允许使用 PHP 驱动真实的 Web 浏览器来创建端到端测试。

安装

1
$ composer require symfony/panther

注意

如果你在 Symfony 应用外部安装此组件,你必须在你的代码中引入 vendor/autoload.php 文件,以启用 Composer 提供的类自动加载机制。阅读 这篇文章 以获取更多细节。

简介

端到端测试是一种特殊的应用测试类型,它模拟真实用户与你的应用进行交互。它们通常用于测试你的应用的用户界面(UI)以及这些交互的效果(例如,当我点击此按钮时,必须发送一封邮件)。与上面详述的功能测试不同的是,端到端测试使用真实的浏览器而不是模拟的浏览器。此浏览器可以在无头模式(没有图形界面)下运行,也可以不在无头模式下运行。第一种选择方便在持续集成(CI)中运行测试,而第二种选择对于调试目的很有用。

这就是 Panther 的目的,Panther 是一个提供真实浏览器来运行你的测试的组件。以下是使 Panther 与 Symfony 提供的其他测试工具相比显得特别的一些方面

  • 可以在测试期间的任何时间点截取浏览器屏幕截图
  • 网页中包含的 JavaScript 代码会被执行
  • Panther 支持 Chrome (或 Firefox) 实现的所有功能
  • 测试实时应用(例如,使用 Mercure 的 WebSockets、服务器发送事件等)的便捷方式

安装 Web 驱动

Panther 使用 WebDriver 协议来控制用于抓取网站的浏览器。在所有系统上,你可以使用 dbrekelmans/browser-driver-installer 在本地安装 ChromeDriver 和 geckodriver

1
2
3
$ composer require --dev dbrekelmans/bdi

$ vendor/bin/bdi detect drivers

Panther 将检测并自动使用存储在你的项目的 drivers/ 目录中的驱动程序(如果手动安装它们)。你可以下载 Chromium 或 Chrome 的 ChromeDriver 和 Firefox 的 GeckoDriver,并将它们放在你的 PATH 中的任何位置或你的项目的 drivers/ 目录中。

或者,你可以使用你的操作系统的包管理器来安装它们

1
2
3
4
5
6
7
8
# Ubuntu
$ apt-get install chromium-chromedriver firefox-geckodriver

# MacOS, using Homebrew
$ brew install chromedriver geckodriver

# Windows, using Chocolatey
$ choco install chromedriver selenium-gecko-driver

注册 PHPUnit 扩展

如果你打算使用 Panther 来测试你的应用,强烈建议注册 Panther PHPUnit 扩展。虽然不是强制性的,但此扩展通过提高性能并允许使用 交互式调试模式,极大地改善了测试体验。

当结合 PANTHER_ERROR_SCREENSHOT_DIR 环境变量使用扩展时,使用 Panther 客户端的失败或错误测试(在客户端创建之后)将自动截取屏幕截图以帮助调试。

要注册 Panther 扩展,请将以下行添加到 phpunit.xml.dist

1
2
3
4
<!-- phpunit.xml.dist -->
<extensions>
    <extension class="Symfony\Component\Panther\ServerExtension"/>
</extensions>

如果没有扩展,Panther 用于为被测应用提供服务的 Web 服务器将按需启动,并在调用 tearDownAfterClass() 时停止。另一方面,当注册扩展时,Web 服务器将仅在最后一个测试之后停止。

用法

这是一个使用 Panther 测试应用的片段示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\Panther\Client;

$client = Client::createChromeClient();
// alternatively, create a Firefox client
$client = Client::createFirefoxClient();

$client->request('GET', 'https://api-platform.com');
$client->clickLink('Getting started');

// wait for an element to be present in the DOM, even if hidden
$crawler = $client->waitFor('#bootstrapping-the-core-library');
// you can also wait for an element to be visible
$crawler = $client->waitForVisibility('#bootstrapping-the-core-library');

// get the text of an element thanks to the query selector syntax
echo $crawler->filter('div:has(> #bootstrapping-the-core-library)')->text();
// take a screenshot of the current page
$client->takeScreenshot('screen.png');

注意

根据规范,WebDriver 实现默认情况下仅返回显示的文本。当你在 head 标签(如 title)上进行过滤时,text() 方法返回空字符串。使用 html() 方法获取标签的完整内容,包括标签本身。

创建一个 TestCase

PantherTestCase 类允许你编写端到端测试。它使用内置的 PHP Web 服务器自动启动你的应用,并让你使用 Panther 抓取它。为了提供你习惯的所有测试工具,它扩展了 PHPUnitTestCase

如果你正在测试 Symfony 应用,PantherTestCase 会自动扩展 WebTestCase 类。这意味着你可以创建功能测试,它可以直接执行你的应用的内核并访问你所有现有的服务。在这种情况下,你可以将 Symfony 提供的所有爬虫测试断言与 Panther 一起使用。

这是一个 PantherTestCase 的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

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

        // 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');

        // ...
    }
}

Panther 客户端带有等待某些异步进程完成的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class HomepageTest extends PantherTestCase
{
    public function testMyApp(): void
    {
        // ...

        // wait for element to be attached to the DOM
        $client->waitFor('.popin');

        // wait for element to be removed from the DOM
        $client->waitForStaleness('.popin');

        // wait for element of the DOM to become visible
        $client->waitForVisibility('.loader');

        // wait for element of the DOM to become hidden
        $client->waitForInvisibility('.loader');

        // wait for text to be inserted in the element content
        $client->waitForElementToContain('.total', '25 €');

        // wait for text to be removed from the element content
        $client->waitForElementToNotContain('.promotion', '5%');

        // wait for the button to become enabled
        $client->waitForEnabled('[type="submit"]');

        // wait for  the button to become disabled
        $client->waitForDisabled('[type="submit"]');

        // wait for the attribute to contain content
        $client->waitForAttributeToContain('.price', 'data-old-price', '25 €');

        // wait for the attribute to not contain content
        $client->waitForAttributeToNotContain('.price', 'data-old-price', '25 €');
    }
}

最后,你还可以对将来会发生的事情进行断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class HomepageTest extends PantherTestCase
{
    public function testMyApp(): void
    {
        // ...

        // element will be attached to the DOM
        $this->assertSelectorWillExist('.popin');

        // element will be removed from the DOM
        $this->assertSelectorWillNotExist('.popin');

        // element will be visible
        $this->assertSelectorWillBeVisible('.loader');

        // element will not be visible
        $this->assertSelectorWillNotBeVisible('.loader');

        // text will be inserted in the element content
        $this->assertSelectorWillContain('.total', '€25');

        // text will be removed from the element content
        $this->assertSelectorWillNotContain('.promotion', '5%');

        // button will be enabled
        $this->assertSelectorWillBeEnabled('[type="submit"]');

        // button will be disabled
        $this->assertSelectorWillBeDisabled('[type="submit"]');

        // attribute will contain content
        $this->assertSelectorAttributeWillContain('.price', 'data-old-price', '€25');

        // attribute will not contain content
        $this->assertSelectorAttributeWillNotContain('.price', 'data-old-price', '€25');
    }
}

然后,你可以使用 PHPUnit 运行此测试,就像你对任何其他测试一样

1
$ ./vendor/bin/phpunit tests/HomepageTest.php

在编写端到端测试时,你应该记住它们比其他测试慢。如果你需要检查 WebDriver 连接在长时间运行的测试期间是否仍然处于活动状态,你可以使用 Client::ping() 方法,该方法根据连接状态返回布尔值。

高级用法

更改 Web 服务器的主机名和端口

如果你想更改内置 Web 服务器使用的主机和/或端口,请将 hostnameport 传递给 createPantherClient() 方法的 $options 参数

1
2
3
4
$client = self::createPantherClient([
    'hostname' => 'example.com', // defaults to 127.0.0.1
    'port' => 8080, // defaults to 9080
]);

使用 Browser-Kit 客户端

Panther 还允许访问其他基于 BrowserKit 的 ClientCrawler 实现。与 Panther 的原生客户端不同,这些替代客户端不支持 JavaScript、CSS 和屏幕截图捕获,但速度更快。有两个替代客户端可用

  • 第一个直接操作 WebTestCase 提供的 Symfony 内核。它是最快的可用客户端,但仅适用于 Symfony 应用。
  • 第二个利用 HttpBrowser。它是 Symfony 内核和 Panther 测试客户端之间的中间层。HttpBrowser 使用 HttpClient 组件 发送真实的 HTTP 请求。它速度很快,可以浏览任何网页,而不仅仅是被测应用的网页。但是,HttpBrowser 不支持 JavaScript 和其他高级功能,因为它完全用 PHP 编写。这个客户端可以在任何 PHP 应用中使用。

由于所有客户端都实现了相同的 API,你可以通过调用适当的工厂方法从一个客户端切换到另一个客户端,从而为每个测试用例带来良好的权衡:是否需要 JavaScript,是否需要针对外部 SSO 进行身份验证等等。

以下是如何检索这些客户端的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace App\Tests;

use Symfony\Component\Panther\Client;
use Symfony\Component\Panther\PantherTestCase;

class AppTest extends PantherTestCase
{
    public function testMyApp(): void
    {
        // retrieve an existing client
        $symfonyClient = static::createClient();
        $httpBrowserClient = static::createHttpBrowserClient();
        $pantherClient = static::createPantherClient();
        $firefoxClient = static::createPantherClient(['browser' => static::FIREFOX]);

        // create a custom client
        $customChromeClient = Client::createChromeClient(null, null, [], 'https://example.com');
        $customFirefoxClient = Client::createFirefoxClient(null, null, [], 'https://example.com');
        $customSeleniumClient = Client::createSeleniumClient('http://127.0.0.1:4444/wd/hub', null, 'https://example.com');

        // if you are testing a Symfony app, you also have access to the kernel
        $kernel = static::createKernel();

        // ...
    }
}

注意

当初始化自定义客户端时,集成的 Web 服务器不会自动启动。如果你想手动启动它,请使用 PantherTestCase::startWebServer()WebServerManager 类。

测试实时应用

Panther 提供了一种便捷的方式来测试具有实时能力的应用,这些应用使用 MercureWebSocket 和类似技术。

PantherTestCase::createAdditionalPantherClient() 方法可以创建额外的、隔离的浏览器,这些浏览器可以与其他浏览器进行交互。例如,这对于测试具有多个用户同时连接的聊天应用非常有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
        $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 访问控制台的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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');
        $performanceLogs = $client->getWebDriver()->manage()->getLog('performance'); // performance logs
    }
}

传递参数给 ChromeDriver

如果需要,你可以配置要传递给 chromedriver 二进制文件的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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', '/');
    }
}

使用代理

要使用代理服务器,你必须设置 PANTHER_CHROME_ARGUMENTS

1
2
# .env.test
PANTHER_CHROME_ARGUMENTS='--proxy-server=socks://127.0.0.1:9050'

将 Selenium 与内置 Web 服务器一起使用

如果你想将 Selenium Grid 与内置 Web 服务器一起使用,你需要按如下方式配置 Panther 客户端

1
2
3
4
5
6
7
8
9
$client = Client::createPantherClient(
    options: [
        'browser' => PantherTestCase::SELENIUM,
    ],
    managerOptions: [
        'host' => 'http://selenium-hub:4444', // the host of the Selenium Server (Grid)
        'capabilities' => DesiredCapabilities::firefox(), // the capabilities of the browser
    ],
);

接受自签名 SSL 证书

要强制 Chrome 接受无效和自签名证书,你可以设置以下环境变量:PANTHER_CHROME_ARGUMENTS='--ignore-certificate-errors'

危险

此选项不安全,仅在开发环境中进行测试时使用,切勿在生产环境中使用(例如,对于 Web 爬虫)。

对于 Firefox,像这样实例化客户端,你可以在客户端创建时执行此操作

1
$client = Client::createFirefoxClient(null, null, ['capabilities' => ['acceptInsecureCerts' => true]]);

使用外部 Web 服务器

有时,重用现有的 Web 服务器配置而不是启动内置 PHP 服务器会更方便。为此,在创建客户端时设置 external_base_uri 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class E2eTest extends PantherTestCase
{
    public function testMyApp(): void
    {
        $pantherClient = static::createPantherClient(['external_base_uri' => 'http://127.0.0.1']);

        // ...
    }
}

注意

当使用外部 Web 服务器时,Panther 将不会启动内置 PHP Web 服务器。

拥有一个多域名应用

你的 PHP/Symfony 应用可能服务于多个不同的域名。由于 Panther 在测试之间将客户端保存在内存中以提高性能,如果你编写多个使用 Panther 进行不同域名测试的测试,则必须在单独的进程中运行测试。

为此,你可以使用原生的 @runInSeparateProcess PHPUnit 注解。这是一个使用 external_base_uri 选项来确定客户端在使用单独进程时使用的域名的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// tests/FirstDomainTest.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',
        ]);

        // ...
    }
}

// tests/SecondDomainTest.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',
        ]);

        // ...
    }
}

与其他测试工具一起使用

如果你想将 Panther 与其他测试工具(如 LiipFunctionalTestBundle)一起使用,或者你只需要使用不同的基类,则可以使用 Symfony\Component\Panther\PantherTestCaseTrait 来增强你现有的测试基础设施,使其具备一些 Panther 机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace App\Tests\Controller;

use Liip\FunctionalTestBundle\Test\WebTestCase;
use Symfony\Component\Panther\PantherTestCaseTrait;

class DefaultControllerTest extends WebTestCase
{
    use PantherTestCaseTrait;

    public function testWithFixtures(): void
    {
        $this->loadFixtures([]); // load your fixtures
        $client = self::createPantherClient(); // create your panther client

        $client->request('GET', '/');

        // ...
    }
}

通过环境变量配置 Panther

可以设置以下环境变量来更改 Panther 的某些行为

PANTHER_NO_HEADLESS
禁用浏览器的无头模式(将显示测试窗口,有助于调试)
PANTHER_WEB_SERVER_DIR
更改项目的文档根目录(默认为 ./public/,相对路径必须以 ./开头
PANTHER_WEB_SERVER_PORT
更改 Web 服务器的端口(默认为 9080
PANTHER_WEB_SERVER_ROUTER
使用 Web 服务器路由器脚本,该脚本在每个 HTTP 请求开始时运行
PANTHER_EXTERNAL_BASE_URI
使用外部 Web 服务器(不会启动 PHP 内置 Web 服务器)
PANTHER_APP_ENV
覆盖传递给运行 PHP 应用的 Web 服务器的 APP_ENV 变量
PANTHER_ERROR_SCREENSHOT_DIR
为你的失败/错误屏幕截图设置一个基本目录(例如 ./var/error-screenshots
PANTHER_DEVTOOLS
切换浏览器的开发者工具(默认为 enabled,有助于调试)
PANTHER_ERROR_SCREENSHOT_ATTACH
将上面提到的屏幕截图以 junit 附件格式添加到测试输出中
PANTHER_NO_REDUCED_MOTION
禁用浏览器中非必要的移动(例如动画)

2.2.0

PANTHER_NO_REDUCED_MOTION 环境变量的支持在 Panther 2.2.0 中添加。

Chrome 特定的环境变量

PANTHER_NO_SANDBOX
禁用 Chrome 的沙箱(不安全,但允许在容器中使用 Panther)
PANTHER_CHROME_ARGUMENTS
自定义 Chrome 参数。你需要将 PANTHER_NO_HEADLESS 设置为 1 才能完全自定义
PANTHER_CHROME_BINARY
使用另一个 google-chrome 二进制文件

Firefox 特定的环境变量

PANTHER_FIREFOX_ARGUMENTS
自定义 Firefox 参数。你需要设置 PANTHER_NO_HEADLESS 才能完全自定义
PANTHER_FIREFOX_BINARY
使用另一个 firefox 二进制文件

更改浏览器窗口的大小

可以控制浏览器窗口的大小。这也控制了屏幕截图的大小。

这是你在 Chrome 中执行此操作的方式

1
$client = Client::createChromeClient(null, ['--window-size=1500,4000']);

你可以通过将 PANTHER_CHROME_ARGUMENTS 环境变量设置为 --window-size=1500,4000 来实现相同的效果。

在 Firefox 上,这是你执行此操作的方式

1
2
3
4
5
use Facebook\WebDriver\WebDriverDimension;

$client = Client::createFirefoxClient();
$size = new WebDriverDimension(1500, 4000);
$client->manage()->window()->setSize($size);

交互模式

Panther 可以在测试套件失败后暂停。借助这段暂停时间,您可以通过 Web 浏览器调查遇到的问题。要启用此模式,您需要使用不带 headless 模式的 --debug PHPUnit 选项

1
2
3
4
5
6
$ PANTHER_NO_HEADLESS=1 bin/phpunit --debug

Test 'App\AdminTest::testLogin' started
Error: something is wrong.

Press enter to continue...

要使用交互模式,必须注册 PHPUnit 扩展

Docker 集成

这是一个最小的 Docker 镜像,可以运行带有 Chrome 和 Firefox 的 Panther

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

然后您可以构建并运行您的镜像

1
2
$ docker build . -t myproject
$ docker run -it -v "$PWD":/srv/myproject -w /srv/myproject myproject bin/phpunit

在你的 CI 中集成 Panther

Github Actions

Panther 可以与 GitHub Actions 开箱即用。这是一个最小的 .github/workflows/panther.yaml 文件,用于运行 Panther 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name: Run Panther tests

on: [ push, pull_request ]

jobs:
  tests:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: "ramsey/composer-install@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

Travis CI

如果您添加 Chrome 插件,Panther 将可以与 Travis CI 开箱即用。这是一个最小的 .travis.yaml 文件,用于运行 Panther 测试

1
2
3
4
5
6
7
8
9
10
11
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

这是一个最小的 .gitlab-ci.yaml 文件,用于通过 Gitlab CI 运行 Panther 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
image: ubuntu

before_script:
  - apt-get update
  - apt-get install software-properties-common -y
  - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
  - apt-get install curl wget php php-cli php8.1 php8.1-common php8.1-curl php8.1-intl php8.1-xml php8.1-opcache php8.1-mbstring php8.1-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('http://getcomposer.org.cn/installer', 'composer-setup.php');"
  - php composer-setup.php --install-dir=/usr/local/bin --filename=composer
  - php -r "unlink('composer-setup.php');"
  - composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

test:
  script:
    - bin/phpunit

AppVeyor

只要安装了 Google Chrome,Panther 就可以与 AppVeyor 开箱即用。这是一个最小的 appveyor.yaml 文件,用于运行 Panther 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 chromedriver firfox selenium-gecko-driver
  - refreshenv
  - cd c:\tools\php80
  - 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 -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

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

已知限制和故障排除

目前不支持以下功能

  • 抓取 XML 文档(仅支持 HTML)
  • 更新现有文档(浏览器主要用于消费数据,而不是创建网页)
  • 使用多维 PHP 数组语法设置表单值
  • 返回 \DOMElement 实例的方法(因为此库在内部使用 WebDriverElement
  • 在 select 中选择无效选项

此外,如果您正在使用 Bootstrap 5,则存在已知问题。它实现了一种滚动效果,这种效果容易误导 Panther。为了解决这个问题,我们建议您通过在样式文件中将 Bootstrap 5 的 $enable-smooth-scroll 变量设置为 false 来禁用此效果

1
$enable-smooth-scroll: false;

使用 PHP 内置服务器时资源未加载

有时,您的资源可能在测试期间无法加载。这是因为 Panther 使用 PHP 内置服务器 来服务您的应用程序。如果资源文件(或任何请求的 URI,不是 .php 文件)不在您的 public 目录中,内置服务器将返回 404 错误。当您让 AssetMapper 组件dev 环境中处理您的应用程序资源时,通常会发生这种情况。

使用 AssetMapper 时,一种解决方案是在运行测试之前 编译资源。这也将加快您的测试速度,因为 Symfony 不需要处理资源,从而允许 PHP 内置服务器直接服务它们。

另一种选择是创建一个名为 tests/router.php 的文件,并将以下内容添加到其中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tests/router.php
if (is_file($_SERVER['DOCUMENT_ROOT'].\DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) {
    return false;
}

$script = 'index.php';

$_SERVER = array_merge($_SERVER, $_ENV);
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].\DIRECTORY_SEPARATOR.$script;

$_SERVER['SCRIPT_NAME'] = \DIRECTORY_SEPARATOR.$script;
$_SERVER['PHP_SELF'] = \DIRECTORY_SEPARATOR.$script;

require $script;

然后在 phpunit.xml.dist 中使用 PANTHER_WEB_SERVER_ROUTER 环境变量将其声明为 Panther 服务器的路由器

1
2
3
4
5
6
7
8
<!-- phpunit.xml.dist -->
<phpunit>
    <!-- ... -->
    <php>
        <!-- ... -->
        <server name="PANTHER_WEB_SERVER_ROUTER" value="./tests/router.php"/>
    </php>
</phpunit>

另请参阅

请参阅 SymfonyCasts 上的 功能测试教程

附加文档

由于 Panther 实现了流行库的 API,您可以找到更多文档

本作品(包括代码示例)根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本