Laravel 浏览器测试

简介

Laravel Dusk 提供了优雅的、易于使用的浏览器自动测试 API。默认情况下,Dusk 不强制你在机器上安装 JDK 或 Selenium,取而代之地,Dusk 基于独立安装的 ChromeDriver。不过,你可以使用任意其他兼容 Selenium 的驱动。


安装

开始之前,需要添加 Composer 依赖 laravel/dusk 到项目:

$ composer require --dev laravel/dusk

注:如果是手动注册 Dusk Service Provider,永远不要将其注册到生产环境,这会导致所有用户都可以登录到应用。

接下来,运行 Artisan 命令 dusk:install

$ php artisan dusk:install

运行完该命令后,会在 tests 目录下新建一个 Browser 目录并包含一个测试示例。接下来,在 .env 文件中设置环境变量 APP_URL,该变量值需要和你在浏览器中用于访问应用的 URL 相匹配。

要运行测试,使用 Artisan 命令 dusk。dusk 命令接收任意 phpunit 支持的参数:

$ php artisan dusk

如果你上一次运行 dusk 命令测试失败,可以通过使用 dusk:fails 命令重新运行首次失败的测试来节省时间:

$ php artisan dusk:fails

管理 ChromeDriver 安装

如果想要安装与 Laravel Dusk 内置版本不同的其他 ChromeDriver,可以使用 dusk:chrome-driver 命令安装:

# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver

# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 74

# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all

注:Dusk 要求 chromedriver 二进制文件具有可执行权限。如果你在运行 Dusk 时遇到问题,可以通过如下命令确保这个二进制文件可执行:chmod -R 0755 vendor/laravel/dusk/bin/。

使用其它浏览器

默认情况下,Dusk 使用 Google Chrome 和独立安装的 ChromeDriver 来运行浏览器测试(Dusk 扩展包自带),不过,我们也可以启动自己的 Selenium 服务器并在浏览器中进行测试。

开始之前,打开 tests/DuskTestCase.php 文件,该文件是应用的 Dusk 测试用例基类。 在该文件中,我们可以移除对 startChromeDriver 方法的调用,这样,就可以阻止 Dusk 自动启动 ChromeDriver:

/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 * @return void
 */
public static function prepare()
{
    // static::startChromeDriver();
}

接下来,可以编辑 driver 方法连接到你所选择的 URL 和接口,此外,你可以编辑需要传递给 WebDriver 的“期望功能”:

/**
 * Create the RemoteWebDriver instance.
 *
 * @return \Facebook\WebDriver\Remote\RemoteWebDriver
 */
protected function driver()
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}

快速入门

生成测试

要生成一个 Dusk 测试,使用 Artisan 命令 dusk:make,生成的测试位于 tests/Browser 目录:

$ php artisan dusk:make LoginTest

运行测试

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

$ php artisan dusk

如果上一次运行 dusk 命令测试失败,可以通过使用 dusk:fails 命令重新运行首次失败的测试来节省时间:

$ php artisan dusk:fails

dusk 命令接收任意 PHPUnit 支持的参数,所以你可以为给定 group 运行测试:

$ php artisan dusk --group=foo

手动启动 ChromeDriver

默认情况下,Dusk 会自动尝试启动 ChromeDriver,如果在你的系统上不生效,你可以在运行 dusk 命令前手动启动 ChromeDriver。如果你选择了手动启动 ChromeDriver,则需要注释掉 tests/DuskTestCase.php 文件中的如下这行代码:

/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 * @return void
 */
public static function prepare()
{
    // static::startChromeDriver();
}

此外,如果不是在 9515 端口上启动 ChromeDriver,需要在同一个类中编辑 driver 方法:

/**
 * Create the RemoteWebDriver instance.
 *
 * @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 LoginTest extends DuskTestCase
{
    use DatabaseMigrations;
    /**
     * A basic browser test example.
     *
     * @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 自动传递给这个回调并且作为主要对象和应用进行交互并生成断言。

创建多个浏览器

有时候需要多个浏览器以便完成测试,例如,在测试通过 Web 套接字进行交互的聊天室页面时就需要多个浏览器。要创建多个浏览器,只需在调用 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();

fitContent 方法会根据内容尺寸来调整浏览器窗口大小:

$browser->fitContent();

当测试失败时,在给屏幕截图之前,Dusk 会先根据内容自动调整浏览器尺寸,可以在测试代码中通过调用 disableFitOnFailure 方法来禁止该特性:

$browser->disableFitOnFailure();

浏览器宏

如果想要定义一个自定义的浏览器方法以便在测试中复用,可以使用 Browser 类提供的 macro 方法。通常,需要在服务提供者的 boot 方法中调用该方法:

<?php
namespace App\Providers;
use Laravel\Dusk\Browser;
use Illuminate\Support\ServiceProvider;
class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register the Dusk's browser macros.
     *
     * @return void
     */
    public function boot()
    {
        Browser::macro('scrollToElement', function ($element = null) {
            $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
            return $this;
        });
    }
}

macro 方法接收方法名作为第一个参数,以及一个闭包作为第二个参数。该闭包会在 Browser 实现上调用该宏方法时执行:

$this->browse(function ($browser) use ($user) {
    $browser->visit('/pay')
            ->scrollToElement('#credit-card-details')
            ->assertSee('Enter Credit Card Details');
});

认证

我们经常会测试需要认证的页面,可以使用 Dusk 的 loginAs 方法用于避免在每个测试中与登录页面进行交互,loginAs 方法接收用户 ID 或用户模型实例:

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

注:使用 loginAs 方法后,用户会话在某个文件中被所有测试维护。

数据库迁移

测试需要迁移时,例如上面的认证示例,不要使用 RefreshDatabase trait,RefreshDatabase trait 会影响不能跨请求应用的数据库事务。取而代之的,我们使用 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;
}

可以使用cookie方法来获取或设置加密cookie的值:

$browser->cookie('name');

$browser->cookie('name', 'Taylor');

我们可以使用plainCookie方法来获取或设置未加密的 cookie 的值:

$browser->plainCookie('name');

$browser->plainCookie('name', 'Taylor');

可以使用deleteCookie方法删除给定的 cookie:

$browser->deleteCookie('name');

截图

可以使用screenshot方法来截取屏幕截图,并将其与给定的文件名一起存储。所有屏幕截图都将存储在tests/Browser/screenshots目录中:

$browser->screenshot('filename');

将控制台输出存储到磁盘

可以使用storeConsoleLog方法将控制台输出以给定的文件名写入磁盘。控制台输出将存储在tests/Browser/console目录中:

$browser->storeConsoleLog('filename');

将页面源存储到磁盘

可以使用storeSource方法将页面的当前源以给定的文件名写入磁盘。页面源将存储在tests/Browser/source目录中:

$browser->storeSource('filename');

与页面元素交互

Dusk 选择器

编写 Dusk 测试最困难的部分之一就是为元素交互选择好的 CSS 选择器,随着时间的推移,前端的改变可能导致 CSS 选择器像下面这样中断测试:

// HTML...
<button>Login</button>
// Test...
$browser->click('.login-page .container div > button');

Dusk 选择器允许你专注于编写高效的测试而不是记住 CSS 选择器。要定义一个选择器,添加 dusk 属性到 HTML 元素,然后,通过在选择器前添加 @ 前缀在 Dusk 测试中操作元素:

// HTML...
<button dusk="login-button">Login</button>
// Test...
$browser->click('@login-button');

点击链接

要点击链接,可以使用浏览器实例上的 clickLink 方法,clickLink 方法将会点击包含给定文本的链接:

$browser->clickLink($linkText);

注:该方法通过 jQuery 进行交互,如果 jQuery 在页面上无效,Dusk 会自动将其注入到页面,以便在测试期间生效。

文本、值 & 属性

获取 & 设置值

Dusk 提供了多个方法用于和页面元素的当前显示文本、值和属性进行交互,例如,要获取匹配给定选择器的元素值,使用 value 方法:

// Retrieve the value...
$value = $browser->value('selector');

// Set the value...
$browser->value('selector', 'value');

可以使用inputValue方法获取具有给定字段名称的输入元素的“值”:

// Retrieve the value of an input element...
$inputValue = $browser->inputValue('field');

获取文本

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

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

获取属性

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

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

使用表单

输入值

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

$browser->type('email', 'test@163.com');

注意,虽然 type 方法在需要的时候可以接收,但是我们也不是必须要传递 CSS 选择器到该方法。如果没有提供 CSS 选择器,Dusk 会搜索包含给定 name 属性的输入字段,最后,Dusk 会尝试查找包含给定 name 属性的 textarea。

要追加文本到某个字段而不清除其值,可以使用 append 方法:

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

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

$browser->clear('email');

可以使用typeSlowly方法指示 Dusk 缓慢键入。默认情况下,Dusk 将在两次按键之间暂停 100 毫秒。要自定义按键之间的时间量,可以将适当的毫秒数作为第三个参数传递给该方法:

$browser->typeSlowly('mobile', '+1 (202) 555-5555');

$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);

可以使用该appendSlowly方法缓慢附加文本:

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

下拉框

要在下拉选择框中选择值,可以使用 select 方法。和 type 方法一样,select 方法不需要完整的 CSS 选择器,当传递值到 select 方法时,需要传递底层 option 值而不是显示文本:

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

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

$browser->select('size');

复选框

要“选择”复选字段,可以使用 check 方法,和其它输入相关方法一样,不需要传入完整的 CSS 选择器,如果找不到精准匹配的选择器,Dusk 会通过一个相匹配 name 属性来搜索复选框:

$browser->check('terms');
$browser->uncheck('terms');

单选按钮

要“选择”一个单选按钮选项,可以使用 radio 方法,和其它输入相关方法一样,不需要完整的 CSS 选择器,如果找不到精准匹配的选择器,Dusk 会搜索匹配 name 和 value 属性的单选框:

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

上传文件

attach 方法可用于上传文件到 file 输入元素,和其它输入相关方法一样,也不需要完整的 CSS 选择器,如果精准匹配的选择器找不到,Dusk 会通过 name 属性搜索匹配的文件输入:

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

注:使用 attach 函数要求系统安装并启用了了 PHP Zip 扩展。

使用键盘

keys 方法允许我们提供比 type 方法更多的复杂输入序列到给定元素。例如,可以存放编辑键及对应输入值,在本例中,shift键会被存放而taylor被输入到与给定选择器匹配的元素中。输入taylor后,otwell会以不带任何编辑键的形式被输入:

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

我们甚至还可以发送“热键”到包含应用的主CSS选择器上:

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

注:所有编辑键都被包裹在 {} 中,并匹配定义在 Facebook\WebDriver\WebDriverKeys 类中的常量,这些常量可以在GitHub上找到。

使用鼠标

点击元素

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

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

clickAtXPath方法可用于“单击”与给定 XPath 表达式匹配的元素:

$browser->clickAtXPath('//div[@class = "selector"]');

clickAtPoint方法可用于在相对于浏览器可见区域的给定坐标对处“单击”最顶部的元素:

$browser->clickAtPoint(0, 0);

doubleClick方法可用于模拟鼠标的双击:

$browser->doubleClick();

rightClick方法可用于模拟鼠标的右键“单击”:

$browser->rightClick();

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

clickAndHold方法可用于模拟鼠标按钮被点击和按住。对releaseMouse方法的后续调用将撤消此行为并释放鼠标按钮:

$browser->clickAndHold()
        ->pause(1000)
        ->releaseMouse();

鼠标悬停

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

最后,可以将元素拖动给定的偏移量:

$browser->dragOffset('.selector', 10, 10);

JavaScript 对话框

Dusk 提供多个方法与 JavaScript 对话框交互:

// Wait for a dialog to appear:
$browser->waitForDialog($seconds = null);
// Assert that a dialog has been displayed and that its message matches the given value:
$browser->assertDialogOpened('value');
// Type the given value in an open JavaScript prompt dialog:
$browser->typeInDialog('Hello World');

要关闭一个打开的 JavaScript 对话框,可以点击确定按钮:

$browser->acceptDialog();

也可以点击取消按钮:

$browser->dismissDialog();

选择器作用域

有时候可能只想要在某个给定选择器上执行一系列操作,例如,我们可能想要在某个表格中断言文本是否存在然后在同一个表格中点击按钮。可以使用 with 方法来完成这一功能,传递给 with 方法的回调中所有被执行的操作都被限制在给定的选择器中:

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

我们可能偶尔需要在当前范围之外执行断言可以使用elsewhere方法来完成此操作:

 $browser->with('.table', function ($table) {
    // Current scope is `body .table`...
    $browser->elsewhere('.page-title', function ($title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });
 });

等待元素

当测试到广泛使用 JavaScript 的应用时,经常需要等待特定元素或数据加载完成之后才能进行测试。Dusk 让这种测试变得简单,通过使用多种方法,你可以在页面上等待元素变得可访问,甚至直到给定 JavaScript 表达式值为 true。

等待

如果需要暂停测试指定毫秒数,可以使用 pause 方法:

$browser->pause(1000);

等待选择器

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

// Wait a maximum of five seconds for the selector...
$browser->waitFor('.selector');

// Wait a maximum of one second for the selector...
$browser->waitFor('.selector', 1);

还可以等到给定选择器在页面消失:

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

选择器作用域(有效的话)

某些情况下,你可能想要等待给定选择器然后和匹配给定选择器的元素进行交互。例如,可能想要等待直到模态窗口有效然后在该窗口中按下“OK”按钮。whenAvailable 方法可以用于此种案例。给定回调中的所有元素操作执行都会限制在特定选择器中:

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

等待文本

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

// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');

// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);

可以使用该waitUntilMissingText方法等待显示的文本从页面中移除:

// Wait a maximum of five seconds for the text to be removed...
$browser->waitUntilMissingText('Hello World');

// Wait a maximum of one second for the text to be removed...
$browser->waitUntilMissingText('Hello World', 1);

等待链接

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

// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');

// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);

等待页面加载

当进行页面断言如 $browser->assertPathIs('/home') 时,如果 window.location.pathname 被异步更新则断言会失败。你可以使用 waitForLocation 方法等待 location 成为给定值:

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

还可以等待命名路由的 location:

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

等待页面重载

如果需要在页面重载后断言,可以使用 waitForReload 方法:

$browser->click('.some-action')
    ->waitForReload()
    ->assertSee('something');
        ```
#### 等待 JavaScript 表达式

有时候可能想要暂定测试直到 JavaScript 表达式值为 true。可以简单通过 `waitUntil` 方法完成这一功能。当暂停表达式时,不需要包含 return 关键词或者结束分号:
```php
// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.dataLoaded');
$browser->waitUntil('App.data.servers.length > 0');
// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);

等待 Vue 表达式

下面的方法可用于等待直到给定 Vue 组件属性包含指定值:

// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Taylor', '@user');
// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');

带回调的等待

Dusk 里面的很多“wait”方法依赖于底层的 waitUsing 方法。我们可以直接使用这个方法来等待给定回调返回 true,waitUsing 方法接收等待的最大秒数、闭包执行的时间间隔、闭包以及可选的失败信息:

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

将元素滚动到视图中

有时我们可能无法单击某个元素,因为它位于浏览器的可视区域之外。scrollIntoView方法将滚动浏览器窗口,直到给定选择器处的元素在视图中:

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

Vue 断言

Dusk 甚至允许我们对 Vue 组件数据的状态进行断言,例如,假应用包含了如下 Vue 组件:

// HTML...
<profile dusk="profile-component"></profile>
// Component Definition...
Vue.component('profile', {
    template: '<div>{{ user.name }}</div>',
    data: function () {
        return {
            user: {
              name: 'Taylor'
            }
        };
    }
});

可以像这样断言 Vue 组件状态:

/**
 * A basic Vue test example.
 *
 * @return void
 */
public function testVue()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertVue('user.name', '迹忆客', '@profile-component');
    });
}

有效的断言

Dusk 提供了多种断言用于应用测试,所有有效的断言罗列如下:

assertTitle
assertTitleContains
assertUrlIs
assertSchemeIs
assertSchemeIsNot
assertHostIs
assertHostIsNot
assertPortIs
assertPortIsNot
assertPathBeginsWith
assertPathIs
assertPathIsNot
assertRouteIs
assertQueryStringHas
assertQueryStringMissing
assertFragmentIs
assertFragmentBeginsWith
assertFragmentIsNot
assertHasCookie
assertHasPlainCookie
assertCookieMissing
assertPlainCookieMissing
assertCookieValue
assertPlainCookieValue
assertSee
assertDontSee
assertSeeIn
assertDontSeeIn
assertSourceHas
assertSourceMissing
assertSeeLink
assertDontSeeLink
assertInputValue
assertInputValueIsNot
assertChecked
assertNotChecked
assertRadioSelected
assertRadioNotSelected
assertSelected
assertNotSelected
assertSelectHasOptions
assertSelectMissingOption
assertSelectMissingOptions
assertSelectHasOption
assertValue
assertAttribute
assertAriaAttribute
assertDataAttribute
assertVisible
assertPresent
assertMissing
assertDialogOpened
assertEnabled
assertDisabled
assertButtonEnabled
assertButtonDisabled
assertFocused
assertNotFocused
assertAuthenticated
assertGuest
assertAuthenticatedAs
assertVue
assertVueIsNot
assertVueContains
assertVueDoesNotContain
*** ## 页面 有时候,测试需要多个复杂动作在一个序列中执行,这会使得测试代码难以阅读和理解。页面允许你使用单个方法定义优雅的可以在给定页面执行的动作,页面还允许你定义指向应用或单个页面的通用选择器的快捷方式。

生成页面

要生成页面对象,使用 Artisan 命令 dusk:page 即可。所有页面对象都位于 tests/Browser/Pages 目录:

$ php artisan dusk:page Login

配置页面

默认情况下,页面有三个方法:urlassertelements,下面我们来讨论 url 和 assert,至于 elements,我们将会在后续选择器速记中详细讨论。

url方法

url 方法会返回代表页面的 URL 路径,Dusk 会在浏览器中导航到页面时使用这个 URL:

/**
 * Get the URL for the page.
 *
 * @return string
 */
public function url()
{
    return '/login';
}

assert方法

assert 方法会生成必要的断言来验证浏览器确实在给定页面,完成这个方法不是必须的;不过,我们可以在需要的时候生成这些断言。这些断言将会在导航到页面时自动运行:

/**
 * Assert that the browser is on the page.
 *
 * @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');

可以使用visitRoute方法导航到命名路由:

$browser->visitRoute('login');

可以使用backforward方法“后退”和“前进”导航:

$browser->back();

$browser->forward();

我们可以使用refresh方法刷新页面:

$browser->refresh();

速记选择器

页面的 elements 方法允许我们为页面的 CSS 选择器定义快速的、易记的快捷方式,例如,让我们为应用登录页面的“email”输入定义一个快捷方式:

/**
 * Get the element shortcuts for the page.
 *
 * @return array
 */
public function elements()
{
    return [
        '@email' => 'input[name=email]',
    ];
}

现在,我们可以在任意你想要试用完整CSS选择器的地方使用速记选择器:

$browser->type('@email', 'jiyik@163.com');

全局速记选择器

安装完 Dusk 后,一个基本的 Page 类会生成到 tests/Browser/Pages 目录下。该类包含一个可用于定义全局速记选择器的 siteElement 方法,所谓全局速记选择器,指的是在整个应用中生效的速记选择器:

/**
 * Get the global element shortcuts for the site.
 *
 * @return array
 */
public static function siteElements()
{
    return [
        '@element' => '#selector',
    ];
}

页面方法

除了页面定义的默认方法外,你还可以定义额外的方法用于测试。例如,假设我们正在构建一个音乐管理应用,应用页面常用的功能就是创建一个播放列表,我们不必在每次测试的时候重复编写创建播放列表的逻辑,只需在页面类中定义一个 createPlaylist 方法即可:

<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class Dashboard extends Page
{
    // Other page methods...
    /**
     * Create a new playlist.
     *
     * @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 绑定。

生成组件

要生成组件,使用 Artisan 命令 dusk:component,新生成的组件位于 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
{
    /**
     * Get the root selector for the component.
     *
     * @return string
     */
    public function selector()
    {
        return '.date-picker';
    }
    /**
     * Assert that the browser page contains the component.
     *
     * @param  Browser  $browser
     * @return void
     */
    public function assert(Browser $browser)
    {
        $browser->assertVisible($this->selector());
    }
    /**
     * Get the element shortcuts for the component.
     *
     * @return array
     */
    public function elements()
    {
        return [
            '@date-field' => 'input.datepicker-input',
            '@month-list' => 'div > div.datepicker-months',
            '@day-list' => 'div > div.datepicker-days',
        ];
    }
    /**
     * Select the given date.
     *
     * @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
{
    /**
     * A basic component test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                    ->within(new DatePicker, function ($browser) {
                        $browser->selectDate(1, 2018);
                    })
                    ->assertSee('January');
        });
    }
}

持续集成

注:添加持续集成配置文件之前,确保 .env.testing 文件包含值为 http://127.0.0.1:8000 的 APP_URL 配置项。

CircleCI

如果你想通过 CircleCI 运行 Dusk 测试,可以使用这个配置文件作为起点,和 Travis CI 一样,我们使用 php artisan serve 命令启动 PHP 自带的 Web 服务器:

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: Start Chrome Driver
                    command: ./vendor/laravel/dusk/bin/chromedriver-linux
                    background: true
                - run:
                    name: Run Laravel Server
                    command: php artisan serve
                    background: true
                - run:
                    name: Run Laravel Dusk Tests
                    command: php artisan dusk
                - store_artifacts:
                    path: tests/Browser/screenshots

Codeship

想要在 Codeship 中运行 Dusk 测试,添加下面的命令到 Codeship 项目,当然,这些命令这是一个入门示例,如果需要的话你可以添加更多额外命令:

$ phpenv local 7.2
$ cp .env.testing .env
$ mkdir -p ./bootstrap/cache
$ composer install --no-interaction --prefer-dist
$ php artisan key:generate
$ 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 测试,可以使用下面这个 .travis.yml 配置。由于 Travis CI 并不是一个图形化的环境,我们还需要一些额外的操作步骤以便启动 Chrome 浏览器。此外,我们将会使用 php artisan serve 来启动 PHP 自带的 Web 服务器:

language: php
​
php:
  - 7.3
addons:
  chrome: stable
install:
  - cp .env.testing .env
  - travis_retry composer install --no-interaction --prefer-dist --no-suggest
  - php artisan key:generate
before_script:
  - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
  - php artisan serve &
script:
  - php artisan dusk

Github Actions

如果你使用 Github Actions 来运行 Dusk 测试,可以使用下面这个配置文件作为起点。和 Travis CI 类似,我们使用 php artisan serve 命令来启动 PHP 内置的 Web 服务器:

name: CI
on: [push]
jobs:
  dusk-php:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Prepare The Environment
        run: cp .env.example .env
      - name: Install Composer Dependencies
        run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
      - name: Generate Application Key
        run: php artisan key:generate
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &
      - name: Run Laravel Server
        run: php artisan serve > /dev/null 2>&1 &
      - name: Run Dusk Tests
        run: php artisan dusk

查看笔记

扫码一下
查看教程更方便