跳至内容

Laravel Dusk

介绍

Laravel Dusk提供了一套简洁易用的浏览器自动化测试 API。默认情况下,Dusk 不需要您在本地计算机上安装 JDK 或 Selenium。相反,Dusk 使用独立的ChromeDriver安装。不过,您可以自由使用任何其他兼容 Selenium 的驱动程序。

安装

首先,您应该安装Google Chrome并将 Composer 依赖项添加laravel/dusk到您的项目中:

1composer require laravel/dusk --dev

如果您正在手动注册 Dusk 的服务提供商,则切勿生产环境中注册它,因为这样做可能会导致任意用户能够通过您的应用程序进行身份验证。

安装 Dusk 软件包后,执行dusk:installArtisan 命令。该dusk:install命令将创建一个tests/Browser目录、一个 Dusk 示例测试,并为您的操作系统安装 Chrome 驱动程序二进制文件:

1php artisan dusk:install

APP_URL接下来,在应用程序文件中设置环境变量.env。此值应与您在浏览器中访问应用程序时使用的 URL 匹配。

管理 ChromeDriver 安装

如果您想安装与 Laravel Dusk 通过dusk:install命令安装的版本不同的 ChromeDriver 版本,您可以使用以下dusk:chrome-driver命令:

1# Install the latest version of ChromeDriver for your OS...
2php artisan dusk:chrome-driver
3 
4# Install a given version of ChromeDriver for your OS...
5php artisan dusk:chrome-driver 86
6 
7# Install a given version of ChromeDriver for all supported OSs...
8php artisan dusk:chrome-driver --all
9 
10# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
11php artisan dusk:chrome-driver --detect

Dusk 要求chromedriver二进制文件可执行。如果您在运行 Dusk 时遇到问题,请使用以下命令确保二进制文件可执行:chmod -R 0755 vendor/laravel/dusk/bin/

使用其他浏览器

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

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

1/**
2 * Prepare for Dusk test execution.
3 *
4 * @beforeClass
5 */
6public static function prepare(): void
7{
8 // static::startChromeDriver();
9}

接下来,您可以修改该driver方法以连接到您选择的 URL 和端口。此外,您还可以修改应传递给 WebDriver 的“所需功能”:

1use Facebook\WebDriver\Remote\RemoteWebDriver;
2 
3/**
4 * Create the RemoteWebDriver instance.
5 */
6protected function driver(): RemoteWebDriver
7{
8 return RemoteWebDriver::create(
9 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
10 );
11}

入门

生成测试

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

1php artisan dusk:make LoginTest

每次测试后重置数据库

你编写的大多数测试都会与从应用程序数据库检索数据的页面进行交互;但是,你的 Dusk 测试永远不应该使用该RefreshDatabasetrait。该RefreshDatabasetrait 会利用数据库事务,而这些事务在 HTTP 请求中不适用或不可用。因此,你有两个选择:DatabaseMigrationstrait 和DatabaseTruncationtrait。

使用数据库迁移

DatabaseMigrations特性会在每次测试前运行数据库迁移。然而,每次测试删除并重新创建数据库表通常比截断表要慢:

1<?php
2 
3use Illuminate\Foundation\Testing\DatabaseMigrations;
4use Laravel\Dusk\Browser;
5 
6uses(DatabaseMigrations::class);
7 
8//
1<?php
2 
3namespace Tests\Browser;
4 
5use Illuminate\Foundation\Testing\DatabaseMigrations;
6use Laravel\Dusk\Browser;
7use Tests\DuskTestCase;
8 
9class ExampleTest extends DuskTestCase
10{
11 use DatabaseMigrations;
12 
13 //
14}

执行 Dusk 测试时可能无法使用 SQLite 内存数据库。由于浏览器在其自己的进程中执行,因此它无法访问其他进程的内存数据库。

使用数据库截断

DatabaseTruncation特性会在首次测试时迁移数据库,以确保数据库表已正确创建。然而,在后续测试中,数据库表将被直接截断,这比重新运行所有数据库迁移操作更能提升速度:

1<?php
2 
3use Illuminate\Foundation\Testing\DatabaseTruncation;
4use Laravel\Dusk\Browser;
5 
6uses(DatabaseTruncation::class);
7 
8//
1<?php
2 
3namespace Tests\Browser;
4 
5use App\Models\User;
6use Illuminate\Foundation\Testing\DatabaseTruncation;
7use Laravel\Dusk\Browser;
8use Tests\DuskTestCase;
9 
10class ExampleTest extends DuskTestCase
11{
12 use DatabaseTruncation;
13 
14 //
15}

默认情况下,此 trait 将截断除该migrations表之外的所有表。如果您想自定义要截断的表,可以$tablesToTruncate在测试类中定义一个属性:

If you are using Pest, you should define properties or methods on the base DuskTestCase class or on any class your test file extends.

1/**
2 * Indicates which tables should be truncated.
3 *
4 * @var array
5 */
6protected $tablesToTruncate = ['users'];

Alternatively, you may define an $exceptTables property on your test class to specify which tables should be excluded from truncation:

1/**
2 * Indicates which tables should be excluded from truncation.
3 *
4 * @var array
5 */
6protected $exceptTables = ['users'];

To specify the database connections that should have their tables truncated, you may define a $connectionsToTruncate property on your test class:

1/**
2 * Indicates which connections should have their tables truncated.
3 *
4 * @var array
5 */
6protected $connectionsToTruncate = ['mysql'];

If you would like to execute code before or after database truncation is performed, you may define beforeTruncatingDatabase or afterTruncatingDatabase methods on your test class:

1/**
2 * Perform any work that should take place before the database has started truncating.
3 */
4protected function beforeTruncatingDatabase(): void
5{
6 //
7}
8 
9/**
10 * Perform any work that should take place after the database has finished truncating.
11 */
12protected function afterTruncatingDatabase(): void
13{
14 //
15}

Running Tests

To run your browser tests, execute the dusk Artisan command:

1php artisan dusk

If you had test failures the last time you ran the dusk command, you may save time by re-running the failing tests first using the dusk:fails command:

1php artisan dusk:fails

The dusk command accepts any argument that is normally accepted by the Pest / PHPUnit test runner, such as allowing you to only run the tests for a given group:

1php artisan dusk --group=foo

If you are using Laravel Sail to manage your local development environment, please consult the Sail documentation on configuring and running Dusk tests.

Manually Starting ChromeDriver

By default, Dusk will automatically attempt to start ChromeDriver. If this does not work for your particular system, you may manually start ChromeDriver before running the dusk command. If you choose to start ChromeDriver manually, you should comment out the following line of your tests/DuskTestCase.php file:

1/**
2 * Prepare for Dusk test execution.
3 *
4 * @beforeClass
5 */
6public static function prepare(): void
7{
8 // static::startChromeDriver();
9}

In addition, if you start ChromeDriver on a port other than 9515, you should modify the driver method of the same class to reflect the correct port:

1use Facebook\WebDriver\Remote\RemoteWebDriver;
2 
3/**
4 * Create the RemoteWebDriver instance.
5 */
6protected function driver(): RemoteWebDriver
7{
8 return RemoteWebDriver::create(
9 'http://localhost:9515', DesiredCapabilities::chrome()
10 );
11}

Environment Handling

To force Dusk to use its own environment file when running tests, create a .env.dusk.{environment} file in the root of your project. For example, if you will be initiating the dusk command from your local environment, you should create a .env.dusk.local file.

When running tests, Dusk will back-up your .env file and rename your Dusk environment to .env. Once the tests have completed, your .env file will be restored.

Browser Basics

Creating Browsers

To get started, let's write a test that verifies we can log into our application. After generating a test, we can modify it to navigate to the login page, enter some credentials, and click the "Login" button. To create a browser instance, you may call the browse method from within your Dusk test:

1<?php
2 
3use App\Models\User;
4use Illuminate\Foundation\Testing\DatabaseMigrations;
5use Laravel\Dusk\Browser;
6 
7uses(DatabaseMigrations::class);
8 
9test('basic example', function () {
10 $user = User::factory()->create([
11 'email' => 'taylor@laravel.com',
12 ]);
13 
14 $this->browse(function (Browser $browser) use ($user) {
15 $browser->visit('/login')
16 ->type('email', $user->email)
17 ->type('password', 'password')
18 ->press('Login')
19 ->assertPathIs('/home');
20 });
21});
1<?php
2 
3namespace Tests\Browser;
4 
5use App\Models\User;
6use Illuminate\Foundation\Testing\DatabaseMigrations;
7use Laravel\Dusk\Browser;
8use Tests\DuskTestCase;
9 
10class ExampleTest extends DuskTestCase
11{
12 use DatabaseMigrations;
13 
14 /**
15 * A basic browser test example.
16 */
17 public function test_basic_example(): void
18 {
19 $user = User::factory()->create([
20 'email' => 'taylor@laravel.com',
21 ]);
22 
23 $this->browse(function (Browser $browser) use ($user) {
24 $browser->visit('/login')
25 ->type('email', $user->email)
26 ->type('password', 'password')
27 ->press('Login')
28 ->assertPathIs('/home');
29 });
30 }
31}

As you can see in the example above, the browse method accepts a closure. A browser instance will automatically be passed to this closure by Dusk and is the main object used to interact with and make assertions against your application.

Creating Multiple Browsers

有时,您可能需要多个浏览器才能正确执行测试。例如,测试与 WebSocket 交互的聊天屏幕可能需要多个浏览器。要创建多个浏览器,只需在browse方法的闭包签名中添加更多浏览器参数即可:

1$this->browse(function (Browser $first, Browser $second) {
2 $first->loginAs(User::find(1))
3 ->visit('/home')
4 ->waitForText('Message');
5 
6 $second->loginAs(User::find(2))
7 ->visit('/home')
8 ->waitForText('Message')
9 ->type('message', 'Hey Taylor')
10 ->press('Send');
11 
12 $first->waitForText('Hey Taylor')
13 ->assertSee('Jeffrey Way');
14});

visit方法可用于导航到应用程序内的给定 URI:

1$browser->visit('/login');

您可以使用该visitRoute方法导航到命名路线

1$browser->visitRoute($routeName, $parameters);

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

1$browser->back();
2 
3$browser->forward();

您可以使用以下refresh方法刷新页面:

1$browser->refresh();

调整浏览器窗口大小

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

1$browser->resize(1920, 1080);

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

1$browser->maximize();

fitContent方法将调整浏览器窗口的大小以匹配其内容的大小:

1$browser->fitContent();

当测试失败时,Dusk 会在截屏前自动调整浏览器大小以适应内容。你可以disableFitOnFailure在测试中调用以下方法来禁用此功能:

1$browser->disableFitOnFailure();

您可以使用该move方法将浏览器窗口移动到屏幕上的其他位置:

1$browser->move($x = 100, $y = 100);

浏览器宏

如果您想定义一个可以在各种测试中复用的自定义浏览器方法,您可以macro在类中使用该方法。通常,您应该从服务提供商的Browser方法中调用此方法 boot

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Support\ServiceProvider;
6use Laravel\Dusk\Browser;
7 
8class DuskServiceProvider extends ServiceProvider
9{
10 /**
11 * Register Dusk's browser macros.
12 */
13 public function boot(): void
14 {
15 Browser::macro('scrollToElement', function (string $element = null) {
16 $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
17 
18 return $this;
19 });
20 }
21}

macro函数接受一个名称作为其第一个参数,一个闭包作为其第二个参数。当在实例上以方法形式调用宏时,宏的闭包将被执行Browser

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

验证

通常,你会测试需要身份验证的页面。你可以使用 Dusk 的loginAs方法,以避免在每次测试时都与应用程序的登录屏幕进行交互。该loginAs方法接受与你的可验证模型或可验证模型实例关联的主键:

1use App\Models\User;
2use Laravel\Dusk\Browser;
3 
4$this->browse(function (Browser $browser) {
5 $browser->loginAs(User::find(1))
6 ->visit('/home');
7});

使用该loginAs方法后,将为文件内的所有测试维护用户会话。

曲奇饼

你可以使用该cookie方法获取或设置加密 Cookie 的值。默认情况下,Laravel 创建的所有 Cookie 都是加密的:

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

您可以使用该plainCookie方法获取或设置未加密的 cookie 的值:

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

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

1$browser->deleteCookie('name');

执行 JavaScript

您可以使用该script方法在浏览器中执行任意 JavaScript 语句:

1$browser->script('document.documentElement.scrollTop = 0');
2 
3$browser->script([
4 'document.body.scrollTop = 0',
5 'document.documentElement.scrollTop = 0',
6]);
7 
8$output = $browser->script('return window.location.pathname');

截屏

你可以使用该screenshot方法截取屏幕截图并以指定的文件名保存。所有屏幕截图将存储在以下tests/Browser/screenshots目录中:

1$browser->screenshot('filename');

responsiveScreenshots方法可用于在各个断点处截取一系列屏幕截图:

1$browser->responsiveScreenshots('filename');

screenshotElement方法可用于截取页面上特定元素的屏幕截图:

1$browser->screenshotElement('#selector', 'filename');

将控制台输出存储到磁盘

你可以使用该storeConsoleLog方法将当前浏览器的控制台输出以指定的文件名写入磁盘。控制台输出将存储在以下tests/Browser/console目录中:

1$browser->storeConsoleLog('filename');

将页面源代码存储到磁盘

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

1$browser->storeSource('filename');

与元素交互

Dusk选择器

选择合适的 CSS 选择器来与元素交互是编写 Dusk 测试最难的部分之一。随着时间的推移,前端的更改可能会导致类似以下 CSS 选择器破坏你的测试:

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

Dusk 选择器让你专注于编写有效的测试,而不是记住 CSS 选择器。要定义选择器,请dusk向 HTML 元素添加一个属性。然后,在与 Dusk 浏览器交互时,在选择器前添加前缀,@即可在测试中操作附加的元素:

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

如果需要,您可以通过 方法来自定义 Dusk 选择器使用的 HTML 属性。通常,此方法应该从应用程序的 方法selectorHtmlAttribute来调用bootAppServiceProvider

1use Laravel\Dusk\Dusk;
2 
3Dusk::selectorHtmlAttribute('data-dusk');

文本、值和属性

检索和设置值

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

1// Retrieve the value...
2$value = $browser->value('selector');
3 
4// Set the value...
5$browser->value('selector', 'value');

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

1$value = $browser->inputValue('field');

检索文本

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

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

检索属性

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

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

与表单交互

输入值

Dusk 提供了多种与表单和输入元素交互的方法。首先,我们来看一个在输入框中输入文本的示例:

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

请注意,尽管该方法在必要时可以接受 CSS 选择器,但我们无需将 CSS 选择器传入该type方法。如果没有提供 CSS 选择器,Dusk 将搜索具有给定属性的input或字段textareaname

要将文本附加到字段而不清除其内容,您可以使用该append方法:

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

您可以使用下列方法清除输入的值clear

1$browser->clear('email');

你可以使用该方法让 Dusk 以较慢的速度输入typeSlowly。默认情况下,Dusk 每次按键之间会暂停 100 毫秒。要自定义按键间隔时间,你可以将适当的毫秒数作为该方法的第三个参数传递:

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

您可以使用以下appendSlowly方法慢慢地附加文本:

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

要选择元素上可用的值select,可以使用该select方法。与该type方法类似,该select方法不需要完整的 CSS 选择器。向该select方法传递值时,应该传递底层选项值,而不是显示文本:

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

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

1$browser->select('size');

通过提供一个数组作为方法的第二个参数select,您可以指示该方法选择多个选项:

1$browser->select('categories', ['Art', 'Music']);

复选框

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

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

uncheck方法可用于“取消选中”复选框输入:

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

单选按钮

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

1$browser->radio('size', 'large');

附加文件

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

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

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

按下按钮

press方法可用于点击页面上的按钮元素。传递给该press方法的参数可以是按钮的显示文本,也可以是 CSS / Dusk 选择器:

1$browser->press('Login');

提交表单时,许多应用程序会在用户按下表单提交按钮后将其禁用,然后在表单提交的 HTTP 请求完成后重新启用该按钮。要按下按钮并等待按钮重新启用,可以使用以下pressAndWaitFor方法:

1// Press the button and wait a maximum of 5 seconds for it to be enabled...
2$browser->pressAndWaitFor('Save');
3 
4// Press the button and wait a maximum of 1 second for it to be enabled...
5$browser->pressAndWaitFor('Save', 1);

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

1$browser->clickLink($linkText);

您可以使用该seeLink方法来确定具有给定显示文本的链接是否在页面上可见:

1if ($browser->seeLink($linkText)) {
2 // ...
3}

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

使用键盘

keys方法允许你为给定元素提供比该type方法通常允许的更复杂的输入序列。例如,你可以指示 Dusk 在输入值时按住修饰键。在此示例中,在与给定选择器匹配的元素中输入shift时,将按住修饰键。输入 后,将输入 ,而无需任何修饰键:taylortaylorswift

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

该方法的另一个有价值的用例keys是将“键盘快捷键”组合发送到应用程序的主要 CSS 选择器:

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

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

流畅的键盘交互

Dusk 还提供了一个withKeyboard方法,允许你通过该类流畅地执行复杂的键盘交互Laravel\Dusk\Keyboard。该类Keyboard提供pressreleasetypepause方法:

1use Laravel\Dusk\Keyboard;
2 
3$browser->withKeyboard(function (Keyboard $keyboard) {
4 $keyboard->press('c')
5 ->pause(1000)
6 ->release('c')
7 ->type(['c', 'e', 'o']);
8});

键盘宏

如果您想定义可在整个测试套件中轻松复用的自定义键盘交互,可以使用macro该类提供的方法。通常,您应该从服务提供者的Keyboard方法中调用此方法 boot

1<?php
2 
3namespace App\Providers;
4 
5use Facebook\WebDriver\WebDriverKeys;
6use Illuminate\Support\ServiceProvider;
7use Laravel\Dusk\Keyboard;
8use Laravel\Dusk\OperatingSystem;
9 
10class DuskServiceProvider extends ServiceProvider
11{
12 /**
13 * Register Dusk's browser macros.
14 */
15 public function boot(): void
16 {
17 Keyboard::macro('copy', function (string $element = null) {
18 $this->type([
19 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
20 ]);
21 
22 return $this;
23 });
24 
25 Keyboard::macro('paste', function (string $element = null) {
26 $this->type([
27 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
28 ]);
29 
30 return $this;
31 });
32 }
33}

macro函数接受一个名称作为其第一个参数,一个闭包作为其第二个参数。当在实例上以方法形式调用宏时,宏的闭包将被执行Keyboard

1$browser->click('@textarea')
2 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
3 ->click('@another-textarea')
4 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());

使用鼠标

点击元素

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

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

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

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

clickAtPoint方法可用于点击相对于浏览器可视区域的给定坐标对的最顶层元素:

1$browser->clickAtPoint($x = 0, $y = 0);

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

1$browser->doubleClick();
2 
3$browser->doubleClick('.selector');

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

1$browser->rightClick();
2 
3$browser->rightClick('.selector');

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

1$browser->clickAndHold('.selector');
2 
3$browser->clickAndHold()
4 ->pause(1000)
5 ->releaseMouse();

controlClick方法可用于模拟ctrl+click浏览器内的事件:

1$browser->controlClick();
2 
3$browser->controlClick('.selector');

鼠标悬停

mouseover当你需要将鼠标移动到与给定 CSS 或 Dusk 选择器匹配的元素上时,可以使用该方法:

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

拖放

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

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

或者,您可以沿一个方向拖动元素:

1$browser->dragLeft('.selector', $pixels = 10);
2$browser->dragRight('.selector', $pixels = 10);
3$browser->dragUp('.selector', $pixels = 10);
4$browser->dragDown('.selector', $pixels = 10);

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

1$browser->dragOffset('.selector', $x = 10, $y = 10);

JavaScript 对话框

Dusk 提供了多种与 JavaScript 对话框交互的方法。例如,你可以使用该waitForDialog方法等待 JavaScript 对话框出现。该方法接受一个可选参数,指示等待对话框出现的秒数:

1$browser->waitForDialog($seconds = null);

assertDialogOpened方法可用于断言对话框已显示并包含给定的消息:

1$browser->assertDialogOpened('Dialog message');

如果 JavaScript 对话框包含Prompts,您可以使用该typeInDialog方法在Prompts中输入一个值:

1$browser->typeInDialog('Hello World');

要通过单击“确定”按钮关闭打开的 JavaScript 对话框,您可以调用该acceptDialog方法:

1$browser->acceptDialog();

要通过单击“取消”按钮关闭打开的 JavaScript 对话框,您可以调用该dismissDialog方法:

1$browser->dismissDialog();

与内联框架交互

如果需要与 iframe 中的元素进行交互,可以使用该withinFrame方法。所有在该方法提供的闭包内发生的元素交互withinFrame都将作用于指定 iframe 的上下文:

1$browser->withinFrame('#credit-card-details', function ($browser) {
2 $browser->type('input[name="cardnumber"]', '4242424242424242')
3 ->type('input[name="exp-date"]', '1224')
4 ->type('input[name="cvc"]', '123')
5 ->press('Pay');
6});

作用域选择器

有时,您可能希望执行多个操作,同时限定给定选择器内所有操作的作用域。例如,您可能希望断言某些文本仅存在于表格中,然后点击该表格中的按钮。您可以使用该with方法来实现此目的。在该方法的闭包中执行的所有操作都with将限定在原始选择器内:

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

你可能偶尔需要在当前作用域之外执行断言。你可以使用elsewhereelsewhereWhenAvailable方法来实现:

1$browser->with('.table', function (Browser $table) {
2 // Current scope is `body .table`...
3 
4 $browser->elsewhere('.page-title', function (Browser $title) {
5 // Current scope is `body .page-title`...
6 $title->assertSee('Hello World');
7 });
8 
9 $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
10 // Current scope is `body .page-title`...
11 $title->assertSee('Hello World');
12 });
13});

等待元素

在测试大量使用 JavaScript 的应用程序时,通常需要“等待”某些元素或数据可用后才能继续测试。Dusk 让这一切变得轻而易举。您可以使用多种方法等待元素在页面上可见,甚至可以等到给定的 JavaScript 表达式计算结果为true

等待

如果您只需要暂停测试给定的毫秒数,请使用该pause方法:

1$browser->pause(1000);

如果仅当满足给定条件时才需要暂停测试true,请使用该pauseIf方法:

1$browser->pauseIf(App::environment('production'), 1000);

同样,如果您需要暂停测试,除非给定条件为true,您可以使用该pauseUnless方法:

1$browser->pauseUnless(App::environment('testing'), 1000);

等待选择器

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

1// Wait a maximum of five seconds for the selector...
2$browser->waitFor('.selector');
3 
4// Wait a maximum of one second for the selector...
5$browser->waitFor('.selector', 1);

您还可以等到与给定选择器匹配的元素包含给定的文本:

1// Wait a maximum of five seconds for the selector to contain the given text...
2$browser->waitForTextIn('.selector', 'Hello World');
3 
4// Wait a maximum of one second for the selector to contain the given text...
5$browser->waitForTextIn('.selector', 'Hello World', 1);

您还可以等到与给定选择器匹配的元素从页面中消失:

1// Wait a maximum of five seconds until the selector is missing...
2$browser->waitUntilMissing('.selector');
3 
4// Wait a maximum of one second until the selector is missing...
5$browser->waitUntilMissing('.selector', 1);

或者,您可以等到与给定选择器匹配的元素被启用或禁用:

1// Wait a maximum of five seconds until the selector is enabled...
2$browser->waitUntilEnabled('.selector');
3 
4// Wait a maximum of one second until the selector is enabled...
5$browser->waitUntilEnabled('.selector', 1);
6 
7// Wait a maximum of five seconds until the selector is disabled...
8$browser->waitUntilDisabled('.selector');
9 
10// Wait a maximum of one second until the selector is disabled...
11$browser->waitUntilDisabled('.selector', 1);

可用时确定范围选择器

有时,您可能希望等待与给定选择器匹配的元素出现,然后再与该元素进行交互。例如,您可能希望等到模态窗口可用,然后在模态窗口中按下“确定”按钮。whenAvailable可以使用 方法来做到这一点。在给定闭包中执行的所有元素操作都将作用域限定在原始选择器内:

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

等待文本

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

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

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

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

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

1// Wait a maximum of five seconds for the link...
2$browser->waitForLink('Create');
3 
4// Wait a maximum of one second for the link...
5$browser->waitForLink('Create', 1);

等待输入

waitForInput方法可用于等待给定的输入字段在页面上可见:

1// Wait a maximum of five seconds for the input...
2$browser->waitForInput($field);
3 
4// Wait a maximum of one second for the input...
5$browser->waitForInput($field, 1);

等待页面位置

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

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

waitForLocation方法还可用于等待当前窗口位置成为完全限定的 URL:

1$browser->waitForLocation('https://example.com/path');

您还可以等待指定路线的位置:

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

等待页面重新加载

如果您需要在执行操作后等待页面重新加载,请使用该waitForReload方法:

1use Laravel\Dusk\Browser;
2 
3$browser->waitForReload(function (Browser $browser) {
4 $browser->press('Submit');
5})
6->assertSee('Success!');

由于通常需要等待页面重新加载,单击按钮后即可完成,因此您可以使用clickAndWaitForReload以下方便的方法:

1$browser->clickAndWaitForReload('.selector')
2 ->assertSee('something');

等待 JavaScript 表达式

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

1// Wait a maximum of five seconds for the expression to be true...
2$browser->waitUntil('App.data.servers.length > 0');
3 
4// Wait a maximum of one second for the expression to be true...
5$browser->waitUntil('App.data.servers.length > 0', 1);

等待 Vue 表达式

waitUntilVue方法waitUntilVueIsNot可用于等待Vue 组件属性具有给定值:

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

等待 JavaScript 事件

waitForEvent方法可用于暂停测试的执行,直到 JavaScript 事件发生:

1$browser->waitForEvent('load');

事件监听器会附加到当前作用域,body默认为元素。使用作用域选择器时,事件监听器会附加到匹配的元素上:

1$browser->with('iframe', function (Browser $iframe) {
2 // Wait for the iframe's load event...
3 $iframe->waitForEvent('load');
4});

您还可以提供一个选择器作为方法的第二个参数,waitForEvent以将事件监听器附加到特定元素:

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

您还可以等待documentwindow对象上的事件:

1// Wait until the document is scrolled...
2$browser->waitForEvent('scroll', 'document');
3 
4// Wait a maximum of five seconds until the window is resized...
5$browser->waitForEvent('resize', 'window', 5);

等待回调

Dusk 中的许多“等待”方法依赖于底层waitUsing方法。你可以直接使用此方法来等待给定的闭包返回true。该waitUsing方法接受等待的最大秒数、闭包执行的间隔、闭包本身以及可选的失败消息:

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

将元素滚动到视图中

有时你可能无法点击某个元素,因为它超出了浏览器的可视区域。该scrollIntoView方法将滚动浏览器窗口,直到给定选择器处的元素位于视图内:

1$browser->scrollIntoView('.selector')
2 ->click('.selector');

可用的断言

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

断言标题

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

1$browser->assertTitle($title);

断言标题包含

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

1$browser->assertTitleContains($title);

断言UrlIs

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

1$browser->assertUrlIs($url);

断言方案

断言当前 URL 方案与给定方案匹配:

1$browser->assertSchemeIs($scheme);

断言方案不是

断言当前 URL 方案与给定方案不匹配:

1$browser->assertSchemeIsNot($scheme);

assertHostIs

断言当前 URL 主机与给定主机匹配:

1$browser->assertHostIs($host);

断言HostIsNot

断言当前 URL 主机与给定的主机不匹配:

1$browser->assertHostIsNot($host);

断言端口

断言当前 URL 端口与给定端口匹配:

1$browser->assertPortIs($port);

断言端口不是

断言当前 URL 端口与给定端口不匹配:

1$browser->assertPortIsNot($port);

断言路径开始于

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

1$browser->assertPathBeginsWith('/home');

断言路径结束于

断言当前 URL 路径以给定路径结尾:

1$browser->assertPathEndsWith('/home');

断言路径包含

断言当前 URL 路径包含给定的路径:

1$browser->assertPathContains('/home');

断言路径

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

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

断言路径不是

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

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

断言路由

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

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

断言查询字符串有

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

1$browser->assertQueryStringHas($name);

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

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

断言查询字符串缺失

断言缺少给定的查询字符串参数:

1$browser->assertQueryStringMissing($name);

assertFragmentIs

断言 URL 的当前哈希片段与给定的片段匹配:

1$browser->assertFragmentIs('anchor');

断言FragmentBeginsWith

断言 URL 的当前哈希片段以给定的片段开头:

1$browser->assertFragmentBeginsWith('anchor');

断言FragmentIsNot

断言 URL 的当前哈希片段与给定的片段不匹配:

1$browser->assertFragmentIsNot('anchor');

断言给定的加密 cookie 存在:

1$browser->assertHasCookie($name);

断言给定的未加密 cookie 存在:

1$browser->assertHasPlainCookie($name);

断言给定的加密 cookie 不存在:

1$browser->assertCookieMissing($name);

断言给定的未加密 cookie 不存在:

1$browser->assertPlainCookieMissing($name);

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

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

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

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

断言

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

1$browser->assertSee($text);

断言不要看

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

1$browser->assertDontSee($text);

assertSeeIn

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

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

断言不要看到

断言选择器中不存在给定的文本:

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

assertSeeAnythingIn

断言选择器中存在任何文本:

1$browser->assertSeeAnythingIn($selector);

assertSeeNothingIn

断言选择器中不存在文本:

1$browser->assertSeeNothingIn($selector);

assertScript

断言给定的 JavaScript 表达式计算结果为给定的值:

1$browser->assertScript('window.isLoaded')
2 ->assertScript('document.readyState', 'complete');

断言源有

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

1$browser->assertSourceHas($code);

断言源缺失

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

1$browser->assertSourceMissing($code);

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

1$browser->assertSeeLink($linkText);

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

1$browser->assertDontSeeLink($linkText);

断言输入值

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

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

断言输入值不是

断言给定的输入字段没有给定的值:

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

assertChecked

断言给定的复选框已被选中:

1$browser->assertChecked($field);

断言未检查

断言给定的复选框未被选中:

1$browser->assertNotChecked($field);

断言不确定

断言给定的复选框处于不确定状态:

1$browser->assertIndeterminate($field);

断言RadioSelected

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

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

断言RadioNotSelected

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

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

断言已选择

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

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

断言未选择

断言给定的下拉菜单没有选择给定的值:

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

断言选择有选项

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

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

断言选择缺失选项

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

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

断言选择有选项

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

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

断言选择缺失选项

断言给定的值不可选:

1$browser->assertSelectMissingOption($field, $value);

断言值

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

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

断言值不是

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

1$browser->assertValueIsNot($selector, $value);

断言属性

断言与给定选择器匹配的元素在提供的属性中具有给定的值:

1$browser->assertAttribute($selector, $attribute, $value);

断言属性缺失

断言与给定选择器匹配的元素缺少提供的属性:

1$browser->assertAttributeMissing($selector, $attribute);

断言属性包含

断言与给定选择器匹配的元素在提供的属性中包含给定的值:

1$browser->assertAttributeContains($selector, $attribute, $value);

assertAttributeDoesntContain

断言与给定选择器匹配的元素不包含所提供的属性中的给定值:

1$browser->assertAttributeDoesntContain($selector, $attribute, $value);

assertAriaAttribute

断言与给定选择器匹配的元素在提供的 aria 属性中具有给定的值:

1$browser->assertAriaAttribute($selector, $attribute, $value);

例如,给定标记<button aria-label="Add"></button>,您可以aria-label像这样对属性进行断言:

1$browser->assertAriaAttribute('button', 'label', 'Add')

断言数据属性

断言与给定选择器匹配的元素在提供的数据属性中具有给定的值:

1$browser->assertDataAttribute($selector, $attribute, $value);

例如,给定标记<tr id="row-1" data-content="attendees"></tr>,您可以data-label像这样对属性进行断言:

1$browser->assertDataAttribute('#row-1', 'content', 'attendees')

断言可见

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

1$browser->assertVisible($selector);

断言存在

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

1$browser->assertPresent($selector);

断言不存在

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

1$browser->assertNotPresent($selector);

断言缺失

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

1$browser->assertMissing($selector);

断言输入存在

断言具有给定名称的输入存在:

1$browser->assertInputPresent($name);

断言输入缺失

断言源中不存在具有给定名称的输入:

1$browser->assertInputMissing($name);

断言DialogOpened

断言带有给定消息的 JavaScript 对话框已打开:

1$browser->assertDialogOpened($message);

断言已启用

断言给定的字段已启用:

1$browser->assertEnabled($field);

断言已禁用

断言给定字段已被禁用:

1$browser->assertDisabled($field);

断言按钮已启用

断言给定的按钮已启用:

1$browser->assertButtonEnabled($button);

断言按钮已禁用

断言给定的按钮已被禁用:

1$browser->assertButtonDisabled($button);

断言聚焦

断言给定字段已获得焦点:

1$browser->assertFocused($field);

断言不聚焦

断言给定字段未获得焦点:

1$browser->assertNotFocused($field);

断言已认证

断言用户已经过身份验证:

1$browser->assertAuthenticated();

断言访客

断言用户未经过身份验证:

1$browser->assertGuest();

断言已认证

断言用户已被认证为给定用户:

1$browser->assertAuthenticatedAs($user);

assertVue

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

1// HTML...
2 
3<profile dusk="profile-component"></profile>
4 
5// Component Definition...
6 
7Vue.component('profile', {
8 template: '<div>{{ user.name }}</div>',
9 
10 data: function () {
11 return {
12 user: {
13 name: 'Taylor'
14 }
15 };
16 }
17});

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

1test('vue', function () {
2 $this->browse(function (Browser $browser) {
3 $browser->visit('/')
4 ->assertVue('user.name', 'Taylor', '@profile-component');
5 });
6});
1/**
2 * A basic Vue test example.
3 */
4public function test_vue(): void
5{
6 $this->browse(function (Browser $browser) {
7 $browser->visit('/')
8 ->assertVue('user.name', 'Taylor', '@profile-component');
9 });
10}

断言VueIsNot

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

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

断言VueContains

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

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

assertVueDoesntContain

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

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

页面

有时,测试需要按顺序执行多个复杂的操作。这可能会使测试更难阅读和理解。Dusk Pages 允许您定义一些富有表现力的操作,然后可以通过一个方法在给定页面上执行。Pages 还允许您为应用程序或单个页面定义常用选择器的快捷方式。

生成页面

要生成页面对象,请执行dusk:pageArtisan 命令。所有页面对象都将放置在应用程序的tests/Browser/Pages目录中:

1php artisan dusk:page Login

配置页面

默认情况下,页面有三种方法:urlassertelements。我们现在将讨论urlassert方法。 该elements方法将在下文中更详细地讨论

方法url

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

1/**
2 * Get the URL for the page.
3 */
4public function url(): string
5{
6 return '/login';
7}

方法assert

assert方法可以进行任何必要的断言,以验证浏览器是否确实位于给定页面上。实际上,此方法中无需放置任何内容;但是,如果您愿意,可以自由地进行这些断言。这些断言将在导航到页面时自动运行:

1/**
2 * Assert that the browser is on the page.
3 */
4public function assert(Browser $browser): void
5{
6 $browser->assertPathIs($this->url());
7}

一旦定义了页面,您就可以使用visit以下命令导航到该页面:

1use Tests\Browser\Pages\Login;
2 
3$browser->visit(new Login);

有时你可能已经在某个页面上了,需要将该页面的选择器和方法“加载”到当前测试上下文中。这种情况很常见,比如按下按钮后被重定向到某个页面,而没有明确导航到该页面。在这种情况下,你可以使用以下on方法来加载页面:

1use Tests\Browser\Pages\CreatePlaylist;
2 
3$browser->visit('/dashboard')
4 ->clickLink('Create Playlist')
5 ->on(new CreatePlaylist)
6 ->assertSee('@create');

简写选择器

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

1/**
2 * Get the element shortcuts for the page.
3 *
4 * @return array<string, string>
5 */
6public function elements(): array
7{
8 return [
9 '@email' => 'input[name=email]',
10 ];
11}

一旦定义了快捷方式,您就可以在通常使用完整 CSS 选择器的任何位置使用简写选择器:

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

全局速记选择器

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

1/**
2 * Get the global element shortcuts for the site.
3 *
4 * @return array<string, string>
5 */
6public static function siteElements(): array
7{
8 return [
9 '@element' => '#selector',
10 ];
11}

页面方法

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

1<?php
2 
3namespace Tests\Browser\Pages;
4 
5use Laravel\Dusk\Browser;
6use Laravel\Dusk\Page;
7 
8class Dashboard extends Page
9{
10 // Other page methods...
11 
12 /**
13 * Create a new playlist.
14 */
15 public function createPlaylist(Browser $browser, string $name): void
16 {
17 $browser->type('name', $name)
18 ->check('share')
19 ->press('Create Playlist');
20 }
21}

定义方法后,您可以在任何使用该页面的测试中使用它。浏览器实例将自动作为第一个参数传递给自定义页面方法:

1use Tests\Browser\Pages\Dashboard;
2 
3$browser->visit(new Dashboard)
4 ->createPlaylist('My Playlist')
5 ->assertSee('My Playlist');

成分

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

生成组件

要生成组件,请执行dusk:componentArtisan 命令。新组件将放置在tests/Browser/Components以下目录中:

1php artisan dusk:component DatePicker

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

1<?php
2 
3namespace Tests\Browser\Components;
4 
5use Laravel\Dusk\Browser;
6use Laravel\Dusk\Component as BaseComponent;
7 
8class DatePicker extends BaseComponent
9{
10 /**
11 * Get the root selector for the component.
12 */
13 public function selector(): string
14 {
15 return '.date-picker';
16 }
17 
18 /**
19 * Assert that the browser page contains the component.
20 */
21 public function assert(Browser $browser): void
22 {
23 $browser->assertVisible($this->selector());
24 }
25 
26 /**
27 * Get the element shortcuts for the component.
28 *
29 * @return array<string, string>
30 */
31 public function elements(): array
32 {
33 return [
34 '@date-field' => 'input.datepicker-input',
35 '@year-list' => 'div > div.datepicker-years',
36 '@month-list' => 'div > div.datepicker-months',
37 '@day-list' => 'div > div.datepicker-days',
38 ];
39 }
40 
41 /**
42 * Select the given date.
43 */
44 public function selectDate(Browser $browser, int $year, int $month, int $day): void
45 {
46 $browser->click('@date-field')
47 ->within('@year-list', function (Browser $browser) use ($year) {
48 $browser->click($year);
49 })
50 ->within('@month-list', function (Browser $browser) use ($month) {
51 $browser->click($month);
52 })
53 ->within('@day-list', function (Browser $browser) use ($day) {
54 $browser->click($day);
55 });
56 }
57}

使用组件

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

1<?php
2 
3use Illuminate\Foundation\Testing\DatabaseMigrations;
4use Laravel\Dusk\Browser;
5use Tests\Browser\Components\DatePicker;
6 
7uses(DatabaseMigrations::class);
8 
9test('basic example', function () {
10 $this->browse(function (Browser $browser) {
11 $browser->visit('/')
12 ->within(new DatePicker, function (Browser $browser) {
13 $browser->selectDate(2019, 1, 30);
14 })
15 ->assertSee('January');
16 });
17});
1<?php
2 
3namespace Tests\Browser;
4 
5use Illuminate\Foundation\Testing\DatabaseMigrations;
6use Laravel\Dusk\Browser;
7use Tests\Browser\Components\DatePicker;
8use Tests\DuskTestCase;
9 
10class ExampleTest extends DuskTestCase
11{
12 /**
13 * A basic component test example.
14 */
15 public function test_basic_example(): void
16 {
17 $this->browse(function (Browser $browser) {
18 $browser->visit('/')
19 ->within(new DatePicker, function (Browser $browser) {
20 $browser->selectDate(2019, 1, 30);
21 })
22 ->assertSee('January');
23 });
24 }
25}

持续集成

大多数 Dusk 持续集成配置都希望你的 Laravel 应用程序使用端口 8000 上的内置 PHP 开发服务器来提供服务。因此,在继续之前,你应该确保你的持续集成环境具有APP_URL环境变量值http://127.0.0.1:8000

Heroku CI

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

1{
2 "environments": {
3 "test": {
4 "buildpacks": [
5 { "url": "heroku/php" },
6 { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }
7 ],
8 "scripts": {
9 "test-setup": "cp .env.testing .env",
10 "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk"
11 }
12 }
13 }
14}

特拉维斯·CI

要在Travis CI上运行 Dusk 测试,请使用以下.travis.yml配置。由于 Travis CI 不是图形环境,我们需要采取一些额外步骤来启动 Chrome 浏览器。此外,我们将使用它php artisan serve来启动 PHP 的内置 Web 服务器:

1language: php
2 
3php:
4 - 8.2
5 
6addons:
7 chrome: stable
8 
9install:
10 - cp .env.testing .env
11 - travis_retry composer install --no-interaction --prefer-dist
12 - php artisan key:generate
13 - php artisan dusk:chrome-driver
14 
15before_script:
16 - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
17 - php artisan serve --no-reload &
18 
19script:
20 - php artisan dusk

GitHub Actions

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

1name: CI
2on: [push]
3jobs:
4 
5 dusk-php:
6 runs-on: ubuntu-latest
7 env:
8 APP_URL: "http://127.0.0.1:8000"
9 DB_USERNAME: root
10 DB_PASSWORD: root
11 MAIL_MAILER: log
12 steps:
13 - uses: actions/checkout@v4
14 - name: Prepare The Environment
15 run: cp .env.example .env
16 - name: Create Database
17 run: |
18 sudo systemctl start mysql
19 mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
20 - name: Install Composer Dependencies
21 run: composer install --no-progress --prefer-dist --optimize-autoloader
22 - name: Generate Application Key
23 run: php artisan key:generate
24 - name: Upgrade Chrome Driver
25 run: php artisan dusk:chrome-driver --detect
26 - name: Start Chrome Driver
27 run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
28 - name: Run Laravel Server
29 run: php artisan serve --no-reload &
30 - name: Run Dusk Tests
31 run: php artisan dusk
32 - name: Upload Screenshots
33 if: failure()
34 uses: actions/upload-artifact@v4
35 with:
36 name: screenshots
37 path: tests/Browser/screenshots
38 - name: Upload Console Logs
39 if: failure()
40 uses: actions/upload-artifact@v4
41 with:
42 name: console
43 path: tests/Browser/console

奇普尔CI

如果您使用Chipper CI运行 Dusk 测试,则可以使用以下配置文件作为起点。我们将使用 PHP 的内置服务器来运行 Laravel,以便监听请求:

1# file .chipperci.yml
2version: 1
3 
4environment:
5 php: 8.2
6 node: 16
7 
8# Include Chrome in the build environment
9services:
10 - dusk
11 
12# Build all commits
13on:
14 push:
15 branches: .*
16 
17pipeline:
18 - name: Setup
19 cmd: |
20 cp -v .env.example .env
21 composer install --no-interaction --prefer-dist --optimize-autoloader
22 php artisan key:generate
23 
24 # Create a dusk env file, ensuring APP_URL uses BUILD_HOST
25 cp -v .env .env.dusk.ci
26 sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
27 
28 - name: Compile Assets
29 cmd: |
30 npm ci --no-audit
31 npm run build
32 
33 - name: Browser Tests
34 cmd: |
35 php -S [::0]:8000 -t public 2>server.log &
36 sleep 2
37 php artisan dusk:chrome-driver $CHROME_DRIVER
38 php artisan dusk --env=ci

要了解有关在 Chipper CI 上运行 Dusk 测试的更多信息,包括如何使用数据库,请查阅官方 Chipper CI 文档