Skip to content

浏览器测试(Laravel Dusk)

介绍

Laravel Dusk 提供了一种表达性强且易于使用的浏览器自动化和测试 API。默认情况下,Dusk 不需要你在机器上安装 JDK 或 Selenium。相反,Dusk 使用独立的 ChromeDriver 安装。不过,你可以自由使用任何其他兼容 Selenium 的驱动程序。

安装

要开始使用,你应该将 laravel/dusk Composer 依赖项添加到你的项目中:

composer require --dev laravel/dusk:"^4.0"

一旦 Dusk 安装完成,你应该注册 Laravel\Dusk\DuskServiceProvider 服务提供者。通常,这将通过 Laravel 的自动服务提供者注册自动完成。

如果你手动注册 Dusk 的服务提供者,你应该绝对不要在生产环境中注册它,因为这样做可能会导致任意用户能够与应用程序进行身份验证。

安装 Dusk 包后,运行 dusk:install Artisan 命令:

php
artisan dusk:install

一个 Browser 目录将会在你的 tests 目录中创建,并包含一个示例测试。接下来,在你的 .env 文件中设置 APP_URL 环境变量。此值应与您在浏览器中访问应用程序时使用的 URL 匹配。

要运行你的测试,使用 dusk Artisan 命令。 dusk 命令接受任何 phpunit 命令也接受的参数:

php
artisan dusk

使用其他浏览器

默认情况下,Dusk 使用 Google Chrome 和独立的 ChromeDriver 安装来运行你的浏览器测试。不过,你可以启动自己的 Selenium 服务器并在任何你希望的浏览器上运行你的测试。

要开始,打开你的 tests/DuskTestCase.php 文件,这是你的应用程序的基本 Dusk 测试用例。在此文件中,你可以删除对 startChromeDriver 方法的调用。这将阻止 Dusk 自动启动 ChromeDriver:

/**
 * 准备 Dusk 测试执行。
 *
 * @beforeClass
 * @return void
 */
public static function prepare()
{
    // static::startChromeDriver();
}

接下来,你可以修改 driver 方法以连接到你选择的 URL 和端口。此外,你可以修改应传递给 WebDriver 的“期望能力”:

/**
 * 创建 RemoteWebDriver 实例。
 *
 * @return \Facebook\WebDriver\Remote\RemoteWebDriver
 */
protected function driver()
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}

入门

生成测试

要生成 Dusk 测试,使用 dusk:make Artisan 命令。生成的测试将放置在 tests/Browser 目录中:

php
artisan dusk:make LoginTest

运行测试

要运行你的浏览器测试,使用 dusk Artisan 命令:

php
artisan dusk

dusk 命令接受任何通常由 PHPUnit 测试运行器接受的参数,允许你仅运行给定 的测试等:

php
artisan dusk --group=foo

手动启动 ChromeDriver

默认情况下,Dusk 将自动尝试启动 ChromeDriver。如果这对你的特定系统不起作用,你可以在运行 dusk 命令之前手动启动 ChromeDriver。如果你选择手动启动 ChromeDriver,你应该注释掉你的 tests/DuskTestCase.php 文件中的以下行:

/**
 * 准备 Dusk 测试执行。
 *
 * @beforeClass
 * @return void
 */
public static function prepare()
{
    // static::startChromeDriver();
}

此外,如果你在 9515 以外的端口上启动 ChromeDriver,你应该修改同一类的 driver 方法:

/**
 * 创建 RemoteWebDriver 实例。
 *
 * @return \Facebook\WebDriver\Remote\RemoteWebDriver
 */
protected function driver()
{
    return RemoteWebDriver::create(
        'http://localhost:9515', DesiredCapabilities::chrome()
    );
}

环境处理

要强制 Dusk 在运行测试时使用自己的环境文件,请在项目根目录中创建一个 .env.dusk.{environment} 文件。例如,如果你将从 local 环境启动 dusk 命令,你应该创建一个 .env.dusk.local 文件。

运行测试时,Dusk 将备份你的 .env 文件并将你的 Dusk 环境重命名为 .env 。测试完成后,你的 .env 文件将被恢复。

创建浏览器

要开始,让我们编写一个测试来验证我们是否可以登录到我们的应用程序。生成测试后,我们可以修改它以导航到登录页面,输入一些凭据,并点击“登录”按钮。要创建浏览器实例,请调用 browse 方法:

<?php

namespace Tests\Browser;

use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * 一个基本的浏览器测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $user = factory(User::class)->create([
            'email' => 'taylor@laravel.com',
        ]);

        $this->browse(function ($browser) use ($user) {
            $browser->visit('/login')
                    ->type('email', $user->email)
                    ->type('password', 'secret')
                    ->press('Login')
                    ->assertPathIs('/home');
        });
    }
}

如上例所示, browse 方法接受一个回调。Dusk 将自动将浏览器实例传递给此回调,并且是用于与应用程序交互和进行断言的主要对象。

此测试可用于测试由 make:auth Artisan 命令生成的登录屏幕。

创建多个浏览器

有时你可能需要多个浏览器才能正确执行测试。例如,可能需要多个浏览器来测试与 websockets 交互的聊天屏幕。要创建多个浏览器,请在传递给 browse 方法的回调的签名中“请求”多个浏览器:

$this->browse(function ($first, $second) {
    $first->loginAs(User::find(1))
          ->visit('/home')
          ->waitForText('Message');

    $second->loginAs(User::find(2))
           ->visit('/home')
           ->waitForText('Message')
           ->type('message', 'Hey Taylor')
           ->press('Send');

    $first->waitForText('Hey Taylor')
          ->assertSee('Jeffrey Way');
});

调整浏览器窗口大小

你可以使用 resize 方法调整浏览器窗口的大小:

$browser->resize(1920, 1080);

可以使用 maximize 方法最大化浏览器窗口:

$browser->maximize();

认证

通常,你将测试需要认证的页面。你可以使用 Dusk 的 loginAs 方法来避免在每次测试期间与登录屏幕交互。 loginAs 方法接受用户 ID 或用户模型实例:

$this->browse(function ($first, $second) {
    $first->loginAs(User::find(1))
          ->visit('/home');
});

使用 loginAs 方法后,用户会话将在文件中的所有测试中保持。

数据库迁移

当你的测试需要迁移时,如上面的认证示例,你绝不应该使用 RefreshDatabase trait。 RefreshDatabase trait 利用数据库事务,这在 HTTP 请求中将不适用。相反,使用 DatabaseMigrations trait:

<?php

namespace Tests\Browser;

use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;
}

与元素交互

Dusk 选择器

选择用于与元素交互的良好 CSS 选择器是编写 Dusk 测试中最困难的部分之一。随着时间的推移,前端更改可能会导致像下面这样的 CSS 选择器破坏你的测试:

// HTML...

<button>Login</button>

// 测试...

$browser->click('.login-page .container div > button');

Dusk 选择器允许你专注于编写有效的测试,而不是记住 CSS 选择器。要定义选择器,请向 HTML 元素添加 dusk 属性。然后,在 Dusk 测试中使用 @ 前缀选择器来操作附加元素:

// HTML...

<button dusk="login-button">Login</button>

// 测试...

$browser->click('@login-button');

点击链接

要点击链接,你可以在浏览器实例上使用 clickLink 方法。 clickLink 方法将点击具有给定显示文本的链接:

$browser->clickLink($linkText);

此方法与 jQuery 交互。如果页面上没有 jQuery,Dusk 将自动将其注入页面中,以便在测试期间可用。

文本、值和属性

检索和设置值

Dusk 提供了几种方法来与页面上元素的当前显示文本、值和属性进行交互。例如,要获取与给定选择器匹配的元素的“值”,请使用 value 方法:

// 检索值...
$value = $browser->value('selector');

// 设置值...
$browser->value('selector', 'value');

检索文本

text 方法可用于检索与给定选择器匹配的元素的显示文本:

$text = $browser->text('selector');

检索属性

最后, attribute 方法可用于检索与给定选择器匹配的元素的属性:

$attribute = $browser->attribute('selector', 'value');

使用表单

输入值

Dusk 提供了多种方法来与表单和输入元素进行交互。首先,让我们看一个将文本输入到输入字段的示例:

$browser->type('email', 'taylor@laravel.com');

请注意,尽管该方法在必要时接受一个,但我们不需要将 CSS 选择器传递给 type 方法。如果未提供 CSS 选择器,Dusk 将搜索具有给定 name 属性的输入字段。最后,Dusk 将尝试找到具有给定 name 属性的 textarea

要在不清除其内容的情况下向字段追加文本,可以使用 append 方法:

$browser->type('tags', 'foo')
        ->append('tags', ', bar, baz');

你可以使用 clear 方法清除输入的值:

$browser->clear('email');

下拉菜单

要在下拉选择框中选择一个值,可以使用 select 方法。与 type 方法一样, select 方法不需要完整的 CSS 选择器。传递值给 select 方法时,你应该传递底层选项值而不是显示文本:

$browser->select('size', 'Large');

你可以通过省略第二个参数来选择一个随机选项:

$browser->select('size');

复选框

要“选中”复选框字段,可以使用 check 方法。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到精确的选择器匹配,Dusk 将搜索具有匹配 name 属性的复选框:

$browser->check('terms');

$browser->uncheck('terms');

单选按钮

要“选择”单选按钮选项,可以使用 radio 方法。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到精确的选择器匹配,Dusk 将搜索具有匹配 namevalue 属性的单选按钮:

$browser->radio('version', 'php7');

附加文件

attach 方法可用于将文件附加到 file 输入元素。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到精确的选择器匹配,Dusk 将搜索具有匹配 name 属性的文件输入:

$browser->attach('photo', __DIR__.'/photos/me.png');

附加功能需要在服务器上安装并启用 Zip PHP 扩展。

使用键盘

keys 方法允许你为给定元素提供比 type 方法通常允许的更复杂的输入序列。例如,你可以在输入值时按住修饰键。在此示例中,将在输入 taylor 时按住 shift 键。输入 taylor 后,将在没有任何修饰键的情况下输入 otwell

$browser->keys('selector', ['{shift}', 'taylor'], 'otwell');

你甚至可以将“热键”发送到包含应用程序的主要 CSS 选择器:

$browser->keys('.app', ['{command}', 'j']);

所有修饰键都用 {} 字符包裹,并匹配 Facebook\WebDriver\WebDriverKeys 类中定义的常量,可以在 GitHub 上找到

使用鼠标

点击元素

click 方法可用于“点击”与给定选择器匹配的元素:

$browser->click('.selector');

鼠标悬停

mouseover 方法可用于在需要将鼠标移动到与给定选择器匹配的元素时:

$browser->mouseover('.selector');

拖放

drag 方法可用于将与给定选择器匹配的元素拖动到另一个元素:

$browser->drag('.from-selector', '.to-selector');

或者,你可以在单个方向上拖动元素:

$browser->dragLeft('.selector', 10);
$browser->dragRight('.selector', 10);
$browser->dragUp('.selector', 10);
$browser->dragDown('.selector', 10);

选择器范围

有时你可能希望在给定选择器内执行多个操作。例如,你可能希望仅在表格中断言某些文本存在,然后在该表格中点击一个按钮。你可以使用 with 方法来实现这一点。在传递给 with 方法的回调中执行的所有操作都将限定在原始选择器内:

$browser->with('.table', function ($table) {
    $table->assertSee('Hello World')
          ->clickLink('Delete');
});

等待元素

在测试大量使用 JavaScript 的应用程序时,通常需要“等待”某些元素或数据可用,然后再继续测试。Dusk 使这变得很简单。使用多种方法,你可以等待页面上的元素可见,甚至可以等待给定的 JavaScript 表达式计算为 true

等待

如果你需要暂停测试一段时间,可以使用 pause 方法:

$browser->pause(1000);

等待选择器

waitFor 方法可用于暂停测试的执行,直到与给定 CSS 选择器匹配的元素显示在页面上。默认情况下,这将在抛出异常之前暂停测试最多五秒钟。如果需要,你可以将自定义超时阈值作为方法的第二个参数传递:

// 最多等待五秒钟以获取选择器...
$browser->waitFor('.selector');

// 最多等待一秒钟以获取选择器...
$browser->waitFor('.selector', 1);

你还可以等待给定选择器从页面中消失:

$browser->waitUntilMissing('.selector');

$browser->waitUntilMissing('.selector', 1);

在可用时限定选择器

有时你可能希望等待给定选择器,然后与匹配该选择器的元素进行交互。例如,你可能希望等待一个模态窗口可用,然后在模态中按下“确定”按钮。在这种情况下,可以使用 whenAvailable 方法。在传递给 whenAvailable 方法的回调中执行的所有元素操作都将限定在原始选择器内:

$browser->whenAvailable('.modal', function ($modal) {
    $modal->assertSee('Hello World')
          ->press('OK');
});

等待文本

waitForText 方法可用于等待给定文本显示在页面上:

// 最多等待五秒钟以获取文本...
$browser->waitForText('Hello World');

// 最多等待一秒钟以获取文本...
$browser->waitForText('Hello World', 1);

等待链接

waitForLink 方法可用于等待给定链接文本显示在页面上:

// 最多等待五秒钟以获取链接...
$browser->waitForLink('Create');

// 最多等待一秒钟以获取链接...
$browser->waitForLink('Create', 1);

等待页面位置

在进行路径断言时,例如 $browser->assertPathIs('/home') ,如果 window.location.pathname 正在异步更新,则断言可能会失败。你可以使用 waitForLocation 方法等待位置为给定值:

$browser->waitForLocation('/secret');

你还可以等待命名路由的位置:

$browser->waitForRoute($routeName, $parameters);

等待页面重新加载

如果你需要在页面重新加载后进行断言,请使用 waitForReload 方法:

$browser->click('.some-action')
        ->waitForReload()
        ->assertSee('something');

等待 JavaScript 表达式

有时你可能希望暂停测试的执行,直到给定的 JavaScript 表达式计算为 true 。你可以使用 waitUntil 方法轻松实现这一点。将表达式传递给此方法时,你不需要包含 return 关键字或结束分号:

// 最多等待五秒钟以使表达式为 true...
$browser->waitUntil('App.dataLoaded');

$browser->waitUntil('App.data.servers.length > 0');

// 最多等待一秒钟以使表达式为 true...
$browser->waitUntil('App.data.servers.length > 0', 1);

使用回调等待

Dusk 中的许多“等待”方法依赖于底层的 waitUsing 方法。你可以直接使用此方法来等待给定回调返回 truewaitUsing 方法接受等待的最大秒数、评估闭包的间隔、闭包和可选的失败消息:

$browser->waitUsing(10, 1, function () use ($something) {
    return $something->isReady();
}, "Something wasn't ready in time.");

进行 Vue 断言

Dusk 甚至允许你对 Vue 组件数据的状态进行断言。例如,假设你的应用程序包含以下 Vue 组件:

// HTML...

<profile dusk="profile-component"></profile>

// 组件定义...

Vue.component('profile', {
    template: '<div>{{ user.name }}</div>',

    data: function () {
        return {
            user: {
              name: 'Taylor'
            }
        };
    }
});

你可以对 Vue 组件的状态进行如下断言:

/**
 * 一个基本的 Vue 测试示例。
 *
 * @return void
 */
public function testVue()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertVue('user.name', 'Taylor', '@profile-component');
    });
}

可用断言

Dusk 提供了多种可以对你的应用程序进行的断言。所有可用的断言都在下面的列表中记录:

assertTitle assertTitleContains assertUrlIs assertPathBeginsWith assertPathIs assertPathIsNot assertRouteIs assertQueryStringHas assertQueryStringMissing assertFragmentIs assertFragmentBeginsWith assertFragmentIsNot assertHasCookie assertCookieMissing assertCookieValue assertPlainCookieValue assertSee assertDontSee assertSeeIn assertDontSeeIn assertSourceHas assertSourceMissing assertSeeLink assertDontSeeLink assertInputValue assertInputValueIsNot assertChecked assertNotChecked assertRadioSelected assertRadioNotSelected assertSelected assertNotSelected assertSelectHasOptions assertSelectMissingOptions assertSelectHasOption assertValue assertVisible assertPresent assertMissing assertDialogOpened assertEnabled assertDisabled assertFocused assertNotFocused assertVue assertVueIsNot assertVueContains assertVueDoesNotContain

assertTitle

断言页面标题与给定文本匹配:

$browser->assertTitle($title);

assertTitleContains

断言页面标题包含给定文本:

$browser->assertTitleContains($title);

assertUrlIs

断言当前 URL(不含查询字符串)与给定字符串匹配:

$browser->assertUrlIs($url);

assertPathBeginsWith

断言当前 URL 路径以给定路径开头:

$browser->assertPathBeginsWith($path);

assertPathIs

断言当前路径与给定路径匹配:

$browser->assertPathIs('/home');

assertPathIsNot

断言当前路径与给定路径不匹配:

$browser->assertPathIsNot('/home');

assertRouteIs

断言当前 URL 与给定命名路由的 URL 匹配:

$browser->assertRouteIs($name, $parameters);

assertQueryStringHas

断言给定查询字符串参数存在:

$browser->assertQueryStringHas($name);

断言给定查询字符串参数存在并具有给定值:

$browser->assertQueryStringHas($name, $value);

assertQueryStringMissing

断言给定查询字符串参数不存在:

$browser->assertQueryStringMissing($name);

assertFragmentIs

断言当前片段与给定片段匹配:

$browser->assertFragmentIs('anchor');

assertFragmentBeginsWith

断言当前片段以给定片段开头:

$browser->assertFragmentBeginsWith('anchor');

assertFragmentIsNot

断言当前片段与给定片段不匹配:

$browser->assertFragmentIsNot('anchor');

assertHasCookie

断言给定 cookie 存在:

$browser->assertHasCookie($name);

assertCookieMissing

断言给定 cookie 不存在:

$browser->assertCookieMissing($name);

assertCookieValue

断言 cookie 具有给定值:

$browser->assertCookieValue($name, $value);

assertPlainCookieValue

断言未加密的 cookie 具有给定值:

$browser->assertPlainCookieValue($name, $value);

assertSee

断言给定文本存在于页面上:

$browser->assertSee($text);

assertDontSee

断言给定文本不存在于页面上:

$browser->assertDontSee($text);

assertSeeIn

断言给定文本存在于选择器内:

$browser->assertSeeIn($selector, $text);

assertDontSeeIn

断言给定文本不存在于选择器内:

$browser->assertDontSeeIn($selector, $text);

assertSourceHas

断言给定源代码存在于页面上:

$browser->assertSourceHas($code);

assertSourceMissing

断言给定源代码不存在于页面上:

$browser->assertSourceMissing($code);

断言给定链接存在于页面上:

$browser->assertSeeLink($linkText);

断言给定链接不存在于页面上:

$browser->assertDontSeeLink($linkText);

assertInputValue

断言给定输入字段具有给定值:

$browser->assertInputValue($field, $value);

assertInputValueIsNot

断言给定输入字段不具有给定值:

$browser->assertInputValueIsNot($field, $value);

assertChecked

断言给定复选框已选中:

$browser->assertChecked($field);

assertNotChecked

断言给定复选框未选中:

$browser->assertNotChecked($field);

assertRadioSelected

断言给定单选字段已选中:

$browser->assertRadioSelected($field, $value);

assertRadioNotSelected

断言给定单选字段未选中:

$browser->assertRadioNotSelected($field, $value);

assertSelected

断言给定下拉菜单已选择给定值:

$browser->assertSelected($field, $value);

assertNotSelected

断言给定下拉菜单未选择给定值:

$browser->assertNotSelected($field, $value);

assertSelectHasOptions

断言给定数组的值可供选择:

$browser->assertSelectHasOptions($field, $values);

assertSelectMissingOptions

断言给定数组的值不可供选择:

$browser->assertSelectMissingOptions($field, $values);

assertSelectHasOption

断言给定字段上可供选择的给定值:

$browser->assertSelectHasOption($field, $value);

assertValue

断言与给定选择器匹配的元素具有给定值:

$browser->assertValue($selector, $value);

assertVisible

断言与给定选择器匹配的元素可见:

$browser->assertVisible($selector);

assertPresent

断言与给定选择器匹配的元素存在:

$browser->assertPresent($selector);

assertMissing

断言与给定选择器匹配的元素不可见:

$browser->assertMissing($selector);

assertDialogOpened

断言打开了具有给定消息的 JavaScript 对话框:

$browser->assertDialogOpened($message);

assertEnabled

断言给定字段已启用:

$browser->assertEnabled($field);

assertDisabled

断言给定字段已禁用:

$browser->assertDisabled($field);

assertFocused

断言给定字段已聚焦:

$browser->assertFocused($field);

assertNotFocused

断言给定字段未聚焦:

$browser->assertNotFocused($field);

assertVue

断言给定 Vue 组件数据属性与给定值匹配:

$browser->assertVue($property, $value, $componentSelector = null);

assertVueIsNot

断言给定 Vue 组件数据属性与给定值不匹配:

$browser->assertVueIsNot($property, $value, $componentSelector = null);

assertVueContains

断言给定 Vue 组件数据属性是一个数组并包含给定值:

$browser->assertVueContains($property, $value, $componentSelector = null);

assertVueDoesNotContain

断言给定 Vue 组件数据属性是一个数组并不包含给定值:

$browser->assertVueDoesNotContain($property, $value, $componentSelector = null);

页面

有时,测试需要按顺序执行多个复杂操作。这可能会使你的测试难以阅读和理解。页面允许你定义可以在给定页面上使用单个方法执行的表达性操作。页面还允许你为应用程序或单个页面定义常用选择器的快捷方式。

生成页面

要生成页面对象,使用 dusk:page Artisan 命令。所有页面对象将放置在 tests/Browser/Pages 目录中:

php
artisan dusk:page Login

配置页面

默认情况下,页面有三个方法: urlassertelements 。我们现在将讨论 urlassert 方法。 elements 方法将在下面 详细讨论

url 方法

url 方法应返回表示页面的 URL 路径。Dusk 将在浏览器中导航到页面时使用此 URL:

/**
 * 获取页面的 URL。
 *
 * @return string
 */
public function url()
{
    return '/login';
}

assert 方法

assert 方法可以进行任何必要的断言,以验证浏览器实际上在给定页面上。完成此方法不是必需的;但是,如果你愿意,可以进行这些断言。这些断言将在导航到页面时自动运行:

/**
 * 断言浏览器在页面上。
 *
 * @return void
 */
public function assert(Browser $browser)
{
    $browser->assertPathIs($this->url());
}

导航到页面

配置页面后,你可以使用 visit 方法导航到它:

use Tests\Browser\Pages\Login;

$browser->visit(new Login);

有时你可能已经在给定页面上,并需要将页面的选择器和方法“加载”到当前测试上下文中。这在按下按钮并被重定向到给定页面而不显式导航到它时很常见。在这种情况下,你可以使用 on 方法加载页面:

use Tests\Browser\Pages\CreatePlaylist;

$browser->visit('/dashboard')
        ->clickLink('Create Playlist')
        ->on(new CreatePlaylist)
        ->assertSee('@create');

简写选择器

页面的 elements 方法允许你为页面上的任何 CSS 选择器定义快速、易于记忆的快捷方式。例如,让我们为应用程序登录页面的“email”输入字段定义一个快捷方式:

/**
 * 获取页面的元素快捷方式。
 *
 * @return array
 */
public function elements()
{
    return [
        '@email' => 'input[name=email]',
    ];
}

现在,你可以在任何使用完整 CSS 选择器的地方使用此简写选择器:

$browser->type('@email', 'taylor@laravel.com');

全局简写选择器

安装 Dusk 后,将在你的 tests/Browser/Pages 目录中放置一个基本 Page 类。此类包含一个 siteElements 方法,可用于定义在整个应用程序的每个页面上都应可用的全局简写选择器:

/**
 * 获取站点的全局元素快捷方式。
 *
 * @return array
 */
public static function siteElements()
{
    return [
        '@element' => '#selector',
    ];
}

页面方法

除了页面上定义的默认方法外,你还可以定义其他方法,这些方法可以在你的测试中使用。例如,假设我们正在构建一个音乐管理应用程序。应用程序的一个页面的常见操作可能是创建播放列表。你可以在每个测试中重写创建播放列表的逻辑,也可以在页面类上定义一个 createPlaylist 方法:

<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;

class Dashboard extends Page
{
    // 其他页面方法...

    /**
     * 创建新播放列表。
     *
     * @param  \Laravel\Dusk\Browser  $browser
     * @param  string  $name
     * @return void
     */
    public function createPlaylist(Browser $browser, $name)
    {
        $browser->type('name', $name)
                ->check('share')
                ->press('Create Playlist');
    }
}

定义方法后,你可以在使用页面的任何测试中使用它。浏览器实例将自动传递给页面方法:

use Tests\Browser\Pages\Dashboard;

$browser->visit(new Dashboard)
        ->createPlaylist('My Playlist')
        ->assertSee('My Playlist');

组件

组件类似于 Dusk 的“页面对象”,但用于在应用程序中重复使用的 UI 和功能片段,例如导航栏或通知窗口。因此,组件不绑定到特定的 URL。

生成组件

要生成组件,使用 dusk:component Artisan 命令。新组件将放置在 test/Browser/Components 目录中:

php
artisan dusk:component DatePicker

如上所示,“日期选择器”是一个可能在应用程序的各种页面上存在的组件示例。在测试套件中的数十个测试中手动编写浏览器自动化逻辑以选择日期可能会变得繁琐。相反,我们可以定义一个 Dusk 组件来表示日期选择器,从而允许我们在组件中封装该逻辑:

<?php

namespace Tests\Browser\Components;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;

class DatePicker extends BaseComponent
{
    /**
     * 获取组件的根选择器。
     *
     * @return string
     */
    public function selector()
    {
        return '.date-picker';
    }

    /**
     * 断言浏览器页面包含组件。
     *
     * @param  Browser  $browser
     * @return void
     */
    public function assert(Browser $browser)
    {
        $browser->assertVisible($this->selector());
    }

    /**
     * 获取组件的元素快捷方式。
     *
     * @return array
     */
    public function elements()
    {
        return [
            '@date-field' => 'input.datepicker-input',
            '@month-list' => 'div > div.datepicker-months',
            '@day-list' => 'div > div.datepicker-days',
        ];
    }

    /**
     * 选择给定日期。
     *
     * @param  \Laravel\Dusk\Browser  $browser
     * @param  int  $month
     * @param  int  $year
     * @return void
     */
    public function selectDate($browser, $month, $year)
    {
        $browser->click('@date-field')
                ->within('@month-list', function ($browser) use ($month) {
                    $browser->click($month);
                })
                ->within('@day-list', function ($browser) use ($day) {
                    $browser->click($day);
                });
    }
}

使用组件

定义组件后,我们可以轻松地从任何测试中在日期选择器中选择日期。而且,如果选择日期所需的逻辑发生变化,我们只需更新组件:

<?php

namespace Tests\Browser;

use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends DuskTestCase
{
    /**
     * 一个基本的组件测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                    ->within(new DatePicker, function ($browser) {
                        $browser->selectDate(1, 2018);
                    })
                    ->assertSee('January');
        });
    }
}

持续集成

CircleCI

CircleCI 1.0

如果您使用 CircleCI 1.0 来运行 Dusk 测试,可以使用此配置文件作为起点。与 TravisCI 类似,我们将使用 php artisan serve 命令来启动 PHP 的内置 Web 服务器:

dependencies:
  pre:
      - curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
      - sudo dpkg -i google-chrome.deb
      - sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome
      - rm google-chrome.deb

test:
    pre:
        - "./vendor/laravel/dusk/bin/chromedriver-linux":
            background: true
        - cp .env.testing .env
        - "php artisan serve":
            background: true

    override:
        - php artisan dusk

CircleCI 2.0

如果您使用 CircleCI 2.0 来运行 Dusk 测试,可以将这些步骤添加到您的构建中:

version: 2
 jobs:
     build:
         steps:
            - run: sudo apt-get install -y libsqlite3-dev
            - run: cp .env.testing .env
            - run: composer install -n --ignore-platform-reqs
            - run: npm install
            - run: npm run production
            - run: vendor/bin/phpunit

            - run:
               name: 启动 Chrome Driver
               command: ./vendor/laravel/dusk/bin/chromedriver-linux
               background: true

            - run:
               name: 运行 Laravel 服务器
               command: php artisan serve
               background: true

            - run:
               name: 运行 Laravel Dusk 测试
               command: php artisan dusk

Codeship

要在 Codeship 上运行 Dusk 测试,请将以下命令添加到您的 Codeship 项目中。当然,这些命令只是一个起点,您可以根据需要添加其他命令:

phpenv local 7.1
cp .env.testing .env
composer install --no-interaction
nohup bash -c "./vendor/laravel/dusk/bin/chromedriver-linux 2>&1 &"
nohup bash -c "php artisan serve 2>&1 &" && sleep 5
php artisan dusk

Heroku CI

要在 Heroku CI 上运行 Dusk 测试,请将以下 Google Chrome buildpack 和脚本添加到您的 Heroku app.json 文件中:

{
  "environments": {
    "test": {
      "buildpacks": [
        { "url": "heroku/php" },
        { "url": "https://github.com/heroku/heroku-buildpack-google-chrome" }
      ],
      "scripts": {
        "test-setup": "cp .env.testing .env",
        "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve > /dev/null 2>&1 &' && php artisan dusk"
      }
    }
  }
}

Travis CI

要在 Travis CI 上运行您的 Dusk 测试,我们需要使用“启用 sudo”的 Ubuntu 14.04 (Trusty) 环境。由于 Travis CI 不是图形环境,我们需要采取一些额外步骤来启动 Chrome 浏览器。此外,我们将使用 php artisan serve 来启动 PHP 的内置 Web 服务器:

sudo: required
dist: trusty

addons:
   chrome: stable

install:
   - cp .env.testing .env
   - travis_retry composer install --no-interaction --prefer-dist --no-suggest

before_script:
   - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
   - php artisan serve &

script:
   - php artisan dusk