[!WARNING]
Pest 4 现在包含自动化浏览器测试,与 Laravel Dusk 相比,它提供了显著的性能和可用性改进。对于新项目,我们建议使用 Pest 进行浏览器测试。
Laravel Dusk 提供了一个富有表现力、易于使用的浏览器自动化和测试 API。默认情况下,Dusk 不需要你在本地计算机上安装 JDK 或 Selenium。相反,Dusk 使用独立的 ChromeDriver 安装。但是,你可以自由使用任何其他与 Selenium 兼容的驱动程序。
要开始使用,你应该安装 Google Chrome 并将 laravel/dusk Composer 依赖项添加到你的项目:
composer require laravel/dusk --dev[!WARNING]
如果你手动注册 Dusk 的服务提供者,你绝不应该在生产环境中注册它,因为这样做可能导致任意用户能够通过你的应用进行认证。
安装 Dusk 包后,执行 dusk:install Artisan 命令。dusk:install 命令将创建一个 tests/Browser 目录、一个示例 Dusk 测试,并为你的操作系统安装 Chrome Driver 二进制文件:
php artisan dusk:install接下来,在你的应用的 .env 文件中设置 APP_URL 环境变量。此值应与你在浏览器中访问应用所使用的 URL 匹配。
[!NOTE]
如果你正在使用 Laravel Sail 管理你的本地开发环境,请同时查阅 Sail 文档中关于 配置和运行 Dusk 测试 的部分。
如果你想安装与 Laravel Dusk 通过 dusk:install 命令安装的 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 86
# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all
# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect[!WARNING]
Dusk 要求chromedriver二进制文件是可执行的。如果你在运行 Dusk 时遇到问题,应确保二进制文件是可执行的,使用以下命令:chmod -R 0755 vendor/laravel/dusk/bin/。
默认情况下,Dusk 使用 Google Chrome 和独立的 ChromeDriver 安装来运行你的浏览器测试。但是,你可以启动自己的 Selenium 服务器,并针对你想要的任何浏览器运行测试。
要开始使用,请打开你的 tests/DuskTestCase.php 文件,这是你应用的基础 Dusk 测试用例。在此文件中,你可以移除对 startChromeDriver 方法的调用。这将阻止 Dusk 自动启动 ChromeDriver:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}接下来,你可以修改 driver 方法以连接到你选择的 URL 和端口。此外,你还可以修改应传递给 WebDriver 的“期望能力”:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}要生成 Dusk 测试,请使用 dusk:make Artisan 命令。生成的测试将放置在 tests/Browser 目录中:
php artisan dusk:make LoginTest你编写的大多数测试都会与从应用数据库检索数据的页面进行交互;但是,你的 Dusk 测试绝不应使用 RefreshDatabase trait。RefreshDatabase trait 利用数据库事务,这在 HTTP 请求之间将不适用或不可用。相反,你有两个选项:DatabaseMigrations trait 和 DatabaseTruncation trait。
DatabaseMigrations trait 将在每次测试之前运行你的数据库迁移。但是,为每次测试删除并重新创建数据库表通常比截断表慢:
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
pest()->use(DatabaseMigrations::class);
//<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
//
}[!WARNING]
执行 Dusk 测试时,不能使用 SQLite 内存数据库。由于浏览器在其自身的进程中执行,它将无法访问其他进程的内存数据库。
DatabaseTruncation trait 将在第一次测试时迁移你的数据库,以确保数据库表已正确创建。然而,在后续测试中,数据库表将仅被截断——相对于重新运行所有数据库迁移,这提供了速度提升:
<?php
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
pest()->use(DatabaseTruncation::class);
//<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseTruncation;
//
}默认情况下,此 trait 将截断除 migrations 表之外的所有表。如果你想自定义应截断的表,你可以在你的测试类上定义一个 $tablesToTruncate 属性:
[!NOTE]
如果你正在使用 Pest,你应该在基础DuskTestCase类或你的测试文件所扩展的任何类上定义属性或方法。
/**
* Indicates which tables should be truncated.
*
* @var array
*/
protected $tablesToTruncate = ['users'];或者,你可以在你的测试类上定义一个 $exceptTables 属性,以指定哪些表应从截断中排除:
/**
* Indicates which tables should be excluded from truncation.
*
* @var array
*/
protected $exceptTables = ['users'];要指定应截断其表的数据库连接,你可以在你的测试类上定义一个 $connectionsToTruncate 属性:
/**
* Indicates which connections should have their tables truncated.
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];如果你想在执行数据库截断之前或之后执行代码,你可以在你的测试类上定义 beforeTruncatingDatabase 或 afterTruncatingDatabase 方法:
/**
* Perform any work that should take place before the database has started truncating.
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* Perform any work that should take place after the database has finished truncating.
*/
protected function afterTruncatingDatabase(): void
{
//
}要运行你的浏览器测试,请执行 dusk Artisan 命令:
php artisan dusk如果你上次运行 dusk 命令时有测试失败,你可以通过先使用 dusk:fails 命令重新运行失败的测试来节省时间:
php artisan dusk:failsdusk 命令接受 Pest / PHPUnit 测试运行器通常接受的任何参数,例如允许你只运行给定 组 的测试:
php artisan dusk --group=foo[!NOTE]
如果你正在使用 Laravel Sail 管理你的本地开发环境,请查阅 Sail 文档中关于 配置和运行 Dusk 测试 的部分。
默认情况下,Dusk 将自动尝试启动 ChromeDriver。如果这不适用于你的特定系统,你可以在运行 dusk 命令之前手动启动 ChromeDriver。如果你选择手动启动 ChromeDriver,你应该注释掉你的 tests/DuskTestCase.php 文件中的以下行:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}此外,如果你在 9515 以外的端口上启动 ChromeDriver,你应该修改同一类的 driver 方法以反映正确的端口:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}要强制 Dusk 在运行测试时使用自己的环境文件,请在项目根目录中创建一个 .env.dusk.{environment} 文件。例如,如果你将从 local 环境启动 dusk 命令,则应创建一个 .env.dusk.local 文件。
运行测试时,Dusk 将备份你的 .env 文件,并将你的 Dusk 环境文件重命名为 .env。测试完成后,你的 .env 文件将被恢复。
要开始使用,我们来编写一个测试,验证我们能否登录到我们的应用。生成测试后,我们可以修改它,使其导航到登录页面,输入一些凭据,然后点击“登录”按钮。要创建浏览器实例,你可以在 Dusk 测试中调用 browse 方法:
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
pest()->use(DatabaseMigrations::class);
test('basic example', function () {
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
});<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* A basic browser test example.
*/
public function test_basic_example(): void
{
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
}
}如上例所示,browse 方法接受一个闭包。Dusk 会自动将一个浏览器实例传递给这个闭包,它是用于与你的应用交互并进行断言的主要对象。
有时你可能需要多个浏览器才能正确执行测试。例如,测试与 websockets 交互的聊天屏幕可能需要多个浏览器。要创建多个浏览器,只需向 browse 方法的闭包签名中添加更多浏览器参数:
$this->browse(function (Browser $first, Browser $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');
});visit 方法可用于导航到你的应用中的给定 URI:
$browser->visit('/login');你可以使用 visitRoute 方法导航到 命名路由:
$browser->visitRoute($routeName, $parameters);你可以使用 back 和 forward 方法进行“后退”和“前进”导航:
$browser->back();
$browser->forward();你可以使用 refresh 方法刷新页面:
$browser->refresh();你可以使用 resize 方法调整浏览器窗口的大小:
$browser->resize(1920, 1080);maximize 方法可用于最大化浏览器窗口:
$browser->maximize();fitContent 方法将调整浏览器窗口大小以匹配其内容的大小:
$browser->fitContent();当测试失败时,Dusk 将在截图之前自动调整浏览器大小以适应内容。你可以在测试中调用 disableFitOnFailure 方法来禁用此功能:
$browser->disableFitOnFailure();你可以使用 move 方法将浏览器窗口移动到屏幕上的不同位置:
$browser->move($x = 100, $y = 100);如果你想定义一个可以在各种测试中重复使用的自定义浏览器方法,你可以在 Browser 类上使用 macro 方法。通常,你应该从 服务提供者 的 boot 方法中调用此方法:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;
class DuskServiceProvider extends ServiceProvider
{
/**
* Register Dusk's browser macros.
*/
public function boot(): void
{
Browser::macro('scrollToElement', function (string $element = null) {
$this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
return $this;
});
}
}macro 函数接受一个名称作为其第一个参数,以及一个闭包作为其第二个参数。当在 Browser 实例上将宏作为方法调用时,宏的闭包将被执行:
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#credit-card-details')
->assertSee('Enter Credit Card Details');
});通常,你将测试需要认证的页面。你可以使用 Dusk 的 loginAs 方法,以避免在每次测试中都与你的应用的登录屏幕进行交互。loginAs 方法接受与你的可认证模型关联的主键或一个可认证模型实例:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});[!WARNING]
使用loginAs方法后,用户会话将在文件中所有测试中保持。
你可以使用 cookie 方法获取或设置加密 cookie 的值。默认情况下,Laravel 创建的所有 cookie 都是加密的:
$browser->cookie('name');
$browser->cookie('name', 'Taylor');你可以使用 plainCookie 方法获取或设置未加密 cookie 的值:
$browser->plainCookie('name');
$browser->plainCookie('name', 'Taylor');你可以使用 deleteCookie 方法删除给定的 cookie:
$browser->deleteCookie('name');你可以使用 script 方法在浏览器中执行任意 JavaScript 语句:
$browser->script('document.documentElement.scrollTop = 0');
$browser->script([
'document.body.scrollTop = 0',
'document.documentElement.scrollTop = 0',
]);
$output = $browser->script('return window.location.pathname');你可以使用 screenshot 方法截取屏幕截图并以给定文件名存储。所有屏幕截图都将存储在 tests/Browser/screenshots 目录中:
$browser->screenshot('filename');responsiveScreenshots 方法可用于在不同断点处截取一系列屏幕截图:
$browser->responsiveScreenshots('filename');screenshotElement 方法可用于截取页面上特定元素的屏幕截图:
$browser->screenshotElement('#selector', 'filename');你可以使用 storeConsoleLog 方法将当前浏览器的控制台输出以给定文件名写入磁盘。控制台输出将存储在 tests/Browser/console 目录中:
$browser->storeConsoleLog('filename');你可以使用 storeSource 方法将当前页面的源代码以给定文件名写入磁盘。页面源代码将存储在 tests/Browser/source 目录中:
$browser->storeSource('filename');为与元素交互选择好的 CSS 选择器是编写 Dusk 测试中最困难的部分之一。随着时间的推移,前端更改可能导致像下面这样的 CSS 选择器破坏你的测试:
// HTML...
<button>Login</button>// Test...
$browser->click('.login-page .container div > button');Dusk 选择器允许你专注于编写有效的测试,而不是记住 CSS 选择器。要定义一个选择器,请向你的 HTML 元素添加一个 dusk 属性。然后,在与 Dusk 浏览器交互时,用 @ 前缀选择器,以便在测试中操作附加的元素:
// HTML...
<button dusk="login-button">Login</button>// Test...
$browser->click('@login-button');如果需要,你可以通过 selectorHtmlAttribute 方法自定义 Dusk 选择器使用的 HTML 属性。通常,此方法应从你的应用的 AppServiceProvider 的 boot 方法中调用:
use Laravel\Dusk\Dusk;
Dusk::selectorHtmlAttribute('data-dusk');Dusk 提供了几种方法来与页面上元素的当前值、显示文本和属性进行交互。例如,要获取与给定 CSS 或 Dusk 选择器匹配的元素的“值”,请使用 value 方法:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');你可以使用 inputValue 方法获取具有给定字段名称的输入元素的“值”:
$value = $browser->inputValue('field');text 方法可用于检索与给定选择器匹配的元素的显示文本:
$text = $browser->text('selector');最后,attribute 方法可用于检索与给定选择器匹配的元素的属性值:
$attribute = $browser->attribute('selector', 'value');Dusk 提供了多种方法来与表单和输入元素进行交互。首先,我们来看一个向输入字段输入文本的示例:
$browser->type('email', 'taylor@laravel.com');请注意,尽管该方法在必要时接受一个 CSS 选择器,但我们并非必须将 CSS 选择器传递给 type 方法。如果未提供 CSS 选择器,Dusk 将搜索具有给定 name 属性的 input 或 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 元素上可用的值,你可以使用 select 方法。与 type 方法类似,select 方法不需要完整的 CSS 选择器。当向 select 方法传递值时,你应该传递底层选项值而不是显示文本:
$browser->select('size', 'Large');你可以通过省略第二个参数来选择一个随机选项:
$browser->select('size');通过将数组作为 select 方法的第二个参数提供,你可以指示该方法选择多个选项:
$browser->select('categories', ['Art', 'Music']);要“勾选”复选框输入,你可以使用 check 方法。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到 CSS 选择器匹配项,Dusk 将搜索具有匹配 name 属性的复选框:
$browser->check('terms');uncheck 方法可用于“取消勾选”复选框输入:
$browser->uncheck('terms');要“选择”一个 radio 输入选项,你可以使用 radio 方法。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到 CSS 选择器匹配项,Dusk 将搜索具有匹配 name 和 value 属性的 radio 输入:
$browser->radio('size', 'large');attach 方法可用于将文件附加到 file 输入元素。与许多其他输入相关方法一样,不需要完整的 CSS 选择器。如果找不到 CSS 选择器匹配项,Dusk 将搜索具有匹配 name 属性的 file 输入:
$browser->attach('photo', __DIR__.'/photos/mountains.png');[!WARNING]
attach 函数需要你的服务器上安装并启用了ZipPHP 扩展。
press 方法可用于点击页面上的按钮元素。press 方法的参数可以是按钮的显示文本或 CSS / Dusk 选择器:
$browser->press('Login');提交表单时,许多应用在按钮被按下后会禁用表单的提交按钮,然后在表单提交的 HTTP 请求完成后重新启用该按钮。要按下按钮并等待按钮重新启用,你可以使用 pressAndWaitFor 方法:
// Press the button and wait a maximum of 5 seconds for it to be enabled...
$browser->pressAndWaitFor('Save');
// Press the button and wait a maximum of 1 second for it to be enabled...
$browser->pressAndWaitFor('Save', 1);要点击链接,你可以使用浏览器实例上的 clickLink 方法。clickLink 方法将点击具有给定显示文本的链接:
$browser->clickLink($linkText);你可以使用 seeLink 方法判断页面上是否存在具有给定显示文本的链接:
if ($browser->seeLink($linkText)) {
// ...
}[!WARNING]
这些方法与 jQuery 交互。如果页面上没有 jQuery,Dusk 将自动将其注入页面,以便在测试期间可用。
keys 方法允许你向给定元素提供比 type 方法通常允许的更复杂的输入序列。例如,你可以指示 Dusk 在输入值时按住修饰键。在此示例中,在将 taylor 输入到与给定选择器匹配的元素时,shift 键将被按住。输入 taylor 后,将不带任何修饰键输入 swift:
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');keys 方法的另一个有价值的用例是向你的应用的主 CSS 选择器发送“键盘快捷键”组合:
$browser->keys('.app', ['{command}', 'j']);[!NOTE]
所有修饰键,例如{command},都用{}字符包裹,并与Facebook\WebDriver\WebDriverKeys类中定义的常量匹配,该类可在 GitHub 上找到。
Dusk 还提供了 withKeyboard 方法,允许你通过 Laravel\Dusk\Keyboard 类流式执行复杂的键盘交互。Keyboard 类提供了 press、release、type 和 pause 方法:
use Laravel\Dusk\Keyboard;
$browser->withKeyboard(function (Keyboard $keyboard) {
$keyboard->press('c')
->pause(1000)
->release('c')
->type(['c', 'e', 'o']);
});如果你想定义自定义键盘交互,以便在整个测试套件中轻松重复使用,你可以使用 Keyboard 类提供的 macro 方法。通常,你应该从 服务提供者 的 boot 方法中调用此方法:
<?php
namespace App\Providers;
use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;
class DuskServiceProvider extends ServiceProvider
{
/**
* Register Dusk's browser macros.
*/
public function boot(): void
{
Keyboard::macro('copy', function (string $element = null) {
$this->type([
OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
]);
return $this;
});
Keyboard::macro('paste', function (string $element = null) {
$this->type([
OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
]);
return $this;
});
}
}macro 函数接受一个名称作为其第一个参数,以及一个闭包作为其第二个参数。当在 Keyboard 实例上将宏作为方法调用时,宏的闭包将被执行:
$browser->click('@textarea')
->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
->click('@another-textarea')
->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());click 方法可用于点击与给定 CSS 或 Dusk 选择器匹配的元素:
$browser->click('.selector');clickAtXPath 方法可用于点击与给定 XPath 表达式匹配的元素:
$browser->clickAtXPath('//div[@class = "selector"]');clickAtPoint 方法可用于点击浏览器可见区域中给定坐标对处最顶层的元素:
$browser->clickAtPoint($x = 0, $y = 0);doubleClick 方法可用于模拟鼠标双击:
$browser->doubleClick();
$browser->doubleClick('.selector');rightClick 方法可用于模拟鼠标右键点击:
$browser->rightClick();
$browser->rightClick('.selector');clickAndHold 方法可用于模拟鼠标按钮被点击并按住。随后调用 releaseMouse 方法将撤销此行为并释放鼠标按钮:
$browser->clickAndHold('.selector');
$browser->clickAndHold()
->pause(1000)
->releaseMouse();controlClick 方法可用于模拟浏览器中的 ctrl+click 事件:
$browser->controlClick();
$browser->controlClick('.selector');当你需要将鼠标移动到与给定 CSS 或 Dusk 选择器匹配的元素上时,可以使用 mouseover 方法:
$browser->mouseover('.selector');drag 方法可用于将与给定选择器匹配的元素拖动到另一个元素:
$browser->drag('.from-selector', '.to-selector');或者,你可以沿单个方向拖动元素:
$browser->dragLeft('.selector', $pixels = 10);
$browser->dragRight('.selector', $pixels = 10);
$browser->dragUp('.selector', $pixels = 10);
$browser->dragDown('.selector', $pixels = 10);最后,你可以通过给定偏移量拖动元素:
$browser->dragOffset('.selector', $x = 10, $y = 10);Dusk 提供了多种方法来与 JavaScript 对话框进行交互。例如,你可以使用 waitForDialog 方法等待 JavaScript 对话框出现。此方法接受一个可选参数,指示等待对话框出现的最大秒数:
$browser->waitForDialog($seconds = null);assertDialogOpened 方法可用于断言对话框已显示并包含给定消息:
$browser->assertDialogOpened('Dialog message');如果 JavaScript 对话框包含提示,你可以使用 typeInDialog 方法向提示中输入值:
$browser->typeInDialog('Hello World');要通过点击“确定”按钮关闭打开的 JavaScript 对话框,你可以调用 acceptDialog 方法:
$browser->acceptDialog();要通过点击“取消”按钮关闭打开的 JavaScript 对话框,你可以调用 dismissDialog 方法:
$browser->dismissDialog();如果你需要与 iframe 中的元素进行交互,你可以使用 withinFrame 方法。在提供给 withinFrame 方法的闭包中发生的所有元素交互都将限定在指定 iframe 的上下文中:
$browser->withinFrame('#credit-card-details', function ($browser) {
$browser->type('input[name="cardnumber"]', '4242424242424242')
->type('input[name="exp-date"]', '1224')
->type('input[name="cvc"]', '123')
->press('Pay');
});有时你可能希望在给定选择器内执行多个操作,并限定所有操作的范围。例如,你可能希望断言某个文本仅存在于表格中,然后点击该表格中的按钮。你可以使用 with 方法来实现这一点。在提供给 with 方法的闭包中执行的所有操作都将限定在原始选择器的范围内:
$browser->with('.table', function (Browser $table) {
$table->assertSee('Hello World')
->clickLink('Delete');
});你可能偶尔需要在当前范围之外执行断言。你可以使用 elsewhere 和 elsewhereWhenAvailable 方法来实现这一点:
$browser->with('.table', function (Browser $table) {
// Current scope is `body .table`...
$browser->elsewhere('.page-title', function (Browser $title) {
// Current scope is `body .page-title`...
$title->assertSee('Hello World');
});
$browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
// Current scope is `body .page-title`...
$title->assertSee('Hello World');
});
});在测试广泛使用 JavaScript 的应用时,通常需要在继续测试之前“等待”某些元素或数据可用。Dusk 使这变得轻而易举。使用各种方法,你可以等待元素在页面上可见,甚至等待直到给定的 JavaScript 表达式评估为 true。
如果你只是需要暂停测试给定的毫秒数,请使用 pause 方法:
$browser->pause(1000);如果你只需要在给定条件为 true 时暂停测试,请使用 pauseIf 方法:
$browser->pauseIf(App::environment('production'), 1000);同样,如果你需要暂停测试,除非给定条件为 true,否则可以使用 pauseUnless 方法:
$browser->pauseUnless(App::environment('testing'), 1000);waitFor 方法可用于暂停测试的执行,直到与给定 CSS 或 Dusk 选择器匹配的元素显示在页面上。默认情况下,这会将测试暂停最多五秒钟,然后抛出异常。如果需要,你可以将自定义超时阈值作为方法的第二个参数传递:
// 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);你也可以等待直到与给定选择器匹配的元素包含给定文本:
// Wait a maximum of five seconds for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World');
// Wait a maximum of one second for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World', 1);你也可以等待直到与给定选择器匹配的元素从页面中消失:
// Wait a maximum of five seconds until the selector is missing...
$browser->waitUntilMissing('.selector');
// Wait a maximum of one second until the selector is missing...
$browser->waitUntilMissing('.selector', 1);或者,你可以等待直到与给定选择器匹配的元素被启用或禁用:
// Wait a maximum of five seconds until the selector is enabled...
$browser->waitUntilEnabled('.selector');
// Wait a maximum of one second until the selector is enabled...
$browser->waitUntilEnabled('.selector', 1);
// Wait a maximum of five seconds until the selector is disabled...
$browser->waitUntilDisabled('.selector');
// Wait a maximum of one second until the selector is disabled...
$browser->waitUntilDisabled('.selector', 1);有时,你可能希望等待与给定选择器匹配的元素出现,然后与该元素进行交互。例如,你可能希望等待模态窗口可用,然后点击模态框内的“确定”按钮。whenAvailable 方法可用于实现此目的。在给定闭包中执行的所有元素操作都将限定在原始选择器的范围内:
$browser->whenAvailable('.modal', function (Browser $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);waitForInput 方法可用于等待直到给定输入字段在页面上可见:
// Wait a maximum of five seconds for the input...
$browser->waitForInput($field);
// Wait a maximum of one second for the input...
$browser->waitForInput($field, 1);当进行路径断言,例如 $browser->assertPathIs('/home') 时,如果 window.location.pathname 正在异步更新,断言可能会失败。你可以使用 waitForLocation 方法等待位置达到给定值:
$browser->waitForLocation('/secret');waitForLocation 方法也可以用于等待当前窗口位置成为一个完全限定的 URL:
$browser->waitForLocation('https://example.com/path');你也可以等待 命名路由 的位置:
$browser->waitForRoute($routeName, $parameters);如果你在执行操作后需要等待页面重新加载,请使用 waitForReload 方法:
use Laravel\Dusk\Browser;
$browser->waitForReload(function (Browser $browser) {
$browser->press('Submit');
})
->assertSee('Success!');由于在点击按钮后通常需要等待页面重新加载,你可以使用 clickAndWaitForReload 方法以方便操作:
$browser->clickAndWaitForReload('.selector')
->assertSee('something');有时你可能希望暂停测试的执行,直到给定的 JavaScript 表达式评估为 true。你可以轻松地使用 waitUntil 方法实现这一点。向此方法传递表达式时,你不需要包含 return 关键字或结尾分号:
// Wait a maximum of five seconds for the expression to be true...
$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);waitUntilVue 和 waitUntilVueIsNot 方法可用于等待直到 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');waitForEvent 方法可用于暂停测试的执行,直到 JavaScript 事件发生:
$browser->waitForEvent('load');事件监听器附加到当前范围,默认情况下是 body 元素。当使用限定范围的选择器时,事件监听器将附加到匹配的元素:
$browser->with('iframe', function (Browser $iframe) {
// Wait for the iframe's load event...
$iframe->waitForEvent('load');
});你还可以将选择器作为 waitForEvent 方法的第二个参数提供,以将事件监听器附加到特定元素:
$browser->waitForEvent('load', '.selector');你也可以等待 document 和 window 对象的事件:
// Wait until the document is scrolled...
$browser->waitForEvent('scroll', 'document');
// Wait a maximum of five seconds until the window is resized...
$browser->waitForEvent('resize', 'window', 5);Dusk 中的许多“等待”方法都依赖于底层的 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');Dusk 提供了多种可针对你的应用进行的断言。所有可用断言都记录在下面的列表中:
assertTitle
assertTitleContains
assertUrlIs
assertSchemeIs
assertSchemeIsNot
assertHostIs
assertHostIsNot
assertPortIs
assertPortIsNot
assertPathBeginsWith
assertPathEndsWith
assertPathContains
assertPathIs
assertPathIsNot
assertRouteIs
assertQueryStringHas
assertQueryStringMissing
assertFragmentIs
assertFragmentBeginsWith
assertFragmentIsNot
assertHasCookie
assertHasPlainCookie
assertCookieMissing
assertPlainCookieMissing
assertCookieValue
assertPlainCookieValue
assertSee
assertDontSee
assertSeeIn
assertDontSeeIn
assertSeeAnythingIn
assertSeeNothingIn
assertCount
assertScript
assertSourceHas
assertSourceMissing
assertSeeLink
assertDontSeeLink
assertInputValue
assertInputValueIsNot
assertChecked
assertNotChecked
assertIndeterminate
assertRadioSelected
assertRadioNotSelected
assertSelected
assertNotSelected
assertSelectHasOptions
assertSelectMissingOptions
assertSelectHasOption
assertSelectMissingOption
assertValue
assertValueIsNot
assertAttribute
assertAttributeMissing
assertAttributeContains
assertAttributeDoesntContain
assertAriaAttribute
assertDataAttribute
assertVisible
assertPresent
assertNotPresent
assertMissing
assertInputPresent
assertInputMissing
assertDialogOpened
assertEnabled
assertDisabled
assertButtonEnabled
assertButtonDisabled
assertFocused
assertNotFocused
assertAuthenticated
assertGuest
assertAuthenticatedAs
assertVue
assertVueIsNot
assertVueContains
assertVueDoesntContain
断言页面标题与给定文本匹配:
$browser->assertTitle($title);断言页面标题包含给定文本:
$browser->assertTitleContains($title);断言当前 URL(不含查询字符串)与给定字符串匹配:
$browser->assertUrlIs($url);断言当前 URL 方案与给定方案匹配:
$browser->assertSchemeIs($scheme);断言当前 URL 方案与给定方案不匹配:
$browser->assertSchemeIsNot($scheme);断言当前 URL 主机与给定主机匹配:
$browser->assertHostIs($host);断言当前 URL 主机与给定主机不匹配:
$browser->assertHostIsNot($host);断言当前 URL 端口与给定端口匹配:
$browser->assertPortIs($port);断言当前 URL 端口与给定端口不匹配:
$browser->assertPortIsNot($port);断言当前 URL 路径以给定路径开头:
$browser->assertPathBeginsWith('/home');断言当前 URL 路径以给定路径结尾:
$browser->assertPathEndsWith('/home');断言当前 URL 路径包含给定路径:
$browser->assertPathContains('/home');断言当前路径与给定路径匹配:
$browser->assertPathIs('/home');断言当前路径与给定路径不匹配:
$browser->assertPathIsNot('/home');断言当前 URL 与给定 命名路由 的 URL 匹配:
$browser->assertRouteIs($name, $parameters);断言给定查询字符串参数存在:
$browser->assertQueryStringHas($name);断言给定查询字符串参数存在并具有给定值:
$browser->assertQueryStringHas($name, $value);断言给定查询字符串参数缺失:
$browser->assertQueryStringMissing($name);断言 URL 的当前哈希片段与给定片段匹配:
$browser->assertFragmentIs('anchor');断言 URL 的当前哈希片段以给定片段开头:
$browser->assertFragmentBeginsWith('anchor');断言 URL 的当前哈希片段与给定片段不匹配:
$browser->assertFragmentIsNot('anchor');断言给定加密 cookie 存在:
$browser->assertHasCookie($name);断言给定未加密 cookie 存在:
$browser->assertHasPlainCookie($name);断言给定加密 cookie 不存在:
$browser->assertCookieMissing($name);断言给定未加密 cookie 不存在:
$browser->assertPlainCookieMissing($name);断言加密 cookie 具有给定值:
$browser->assertCookieValue($name, $value);断言未加密 cookie 具有给定值:
$browser->assertPlainCookieValue($name, $value);断言给定文本存在于页面上:
$browser->assertSee($text);断言给定文本不存在于页面上:
$browser->assertDontSee($text);断言给定文本存在于选择器内:
$browser->assertSeeIn($selector, $text);断言给定文本不存在于选择器内:
$browser->assertDontSeeIn($selector, $text);断言任何文本都存在于选择器内:
$browser->assertSeeAnythingIn($selector);断言选择器内不存在任何文本:
$browser->assertSeeNothingIn($selector);断言与给定选择器匹配的元素出现指定次数:
$browser->assertCount($selector, $count);断言给定 JavaScript 表达式评估为给定值:
$browser->assertScript('window.isLoaded')
->assertScript('document.readyState', 'complete');断言给定源代码存在于页面上:
$browser->assertSourceHas($code);断言给定源代码不存在于页面上:
$browser->assertSourceMissing($code);断言给定链接存在于页面上:
$browser->assertSeeLink($linkText);断言给定链接不存在于页面上:
$browser->assertDontSeeLink($linkText);断言给定输入字段具有给定值:
$browser->assertInputValue($field, $value);断言给定输入字段不具有给定值:
$browser->assertInputValueIsNot($field, $value);断言给定复选框已选中:
$browser->assertChecked($field);断言给定复选框未选中:
$browser->assertNotChecked($field);断言给定复选框处于不确定状态:
$browser->assertIndeterminate($field);断言给定单选字段已选中:
$browser->assertRadioSelected($field, $value);断言给定单选字段未选中:
$browser->assertRadioNotSelected($field, $value);断言给定下拉列表已选中给定值:
$browser->assertSelected($field, $value);断言给定下拉列表未选中给定值:
$browser->assertNotSelected($field, $value);断言给定值数组可供选择:
$browser->assertSelectHasOptions($field, $values);断言给定值数组不可供选择:
$browser->assertSelectMissingOptions($field, $values);断言给定值在给定字段上可供选择:
$browser->assertSelectHasOption($field, $value);断言给定值不可供选择:
$browser->assertSelectMissingOption($field, $value);断言与给定选择器匹配的元素具有给定值:
$browser->assertValue($selector, $value);断言与给定选择器匹配的元素不具有给定值:
$browser->assertValueIsNot($selector, $value);断言与给定选择器匹配的元素在提供的属性中具有给定值:
$browser->assertAttribute($selector, $attribute, $value);断言与给定选择器匹配的元素缺失提供的属性:
$browser->assertAttributeMissing($selector, $attribute);断言与给定选择器匹配的元素在提供的属性中包含给定值:
$browser->assertAttributeContains($selector, $attribute, $value);断言与给定选择器匹配的元素在提供的属性中不包含给定值:
$browser->assertAttributeDoesntContain($selector, $attribute, $value);断言与给定选择器匹配的元素在提供的 aria 属性中具有给定值:
$browser->assertAriaAttribute($selector, $attribute, $value);例如,给定标记 <button aria-label="Add"></button>,你可以像这样断言 aria-label 属性:
$browser->assertAriaAttribute('button', 'label', 'Add')断言与给定选择器匹配的元素在提供的数据属性中具有给定值:
$browser->assertDataAttribute($selector, $attribute, $value);例如,给定标记 <tr id="row-1" data-content="attendees"></tr>,你可以像这样断言 data-label 属性:
$browser->assertDataAttribute('#row-1', 'content', 'attendees')断言与给定选择器匹配的元素可见:
$browser->assertVisible($selector);断言与给定选择器匹配的元素存在于源代码中:
$browser->assertPresent($selector);断言与给定选择器匹配的元素不存在于源代码中:
$browser->assertNotPresent($selector);断言与给定选择器匹配的元素不可见:
$browser->assertMissing($selector);断言具有给定名称的输入框存在:
$browser->assertInputPresent($name);断言具有给定名称的输入框不存在于源代码中:
$browser->assertInputMissing($name);断言已打开一个包含给定消息的 JavaScript 对话框:
$browser->assertDialogOpened($message);断言给定字段已启用:
$browser->assertEnabled($field);断言给定字段已禁用:
$browser->assertDisabled($field);断言给定按钮已启用:
$browser->assertButtonEnabled($button);断言给定按钮已禁用:
$browser->assertButtonDisabled($button);断言给定字段已聚焦:
$browser->assertFocused($field);断言给定字段未聚焦:
$browser->assertNotFocused($field);断言用户已认证:
$browser->assertAuthenticated();断言用户未认证:
$browser->assertGuest();断言用户已认证为给定用户:
$browser->assertAuthenticatedAs($user);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 组件的状态:
test('vue', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
});/**
* A basic Vue test example.
*/
public function test_vue(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
}断言给定 Vue 组件数据属性与给定值不匹配:
$browser->assertVueIsNot($property, $value, $componentSelector = null);断言给定 Vue 组件数据属性是一个数组并包含给定值:
$browser->assertVueContains($property, $value, $componentSelector = null);断言给定 Vue 组件数据属性是一个数组且不包含给定值:
$browser->assertVueDoesntContain($property, $value, $componentSelector = null);有时,测试需要按顺序执行几个复杂的动作。这会使你的测试更难阅读和理解。Dusk 页面对象允许你定义富有表现力的动作,然后通过单个方法在给定页面上执行这些动作。页面对象还允许你为你的应用或单个页面定义常用选择器的快捷方式。
要生成页面对象,请执行 dusk:page Artisan 命令。所有页面对象都将放置在你的应用的 tests/Browser/Pages 目录中:
php artisan dusk:page Login默认情况下,页面对象有三个方法:url、assert 和 elements。我们现在将讨论 url 和 assert 方法。elements 方法将在 下面更详细地讨论。
url 方法url 方法应返回代表该页面的 URL 路径。Dusk 在浏览器中导航到该页面时将使用此 URL:
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/login';
}assert 方法assert 方法可以进行任何必要的断言,以验证浏览器是否确实在给定页面上。实际上没有必要在此方法中放置任何内容;但是,如果你愿意,可以进行这些断言。这些断言将在导航到页面时自动运行:
/**
* Assert that the browser is on the page.
*/
public function assert(Browser $browser): void
{
$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 选择器定义快速、易于记忆的快捷方式。例如,我们来为应用登录页面的“电子邮件”输入字段定义一个快捷方式:
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@email' => 'input[name=email]',
];
}定义快捷方式后,你可以在通常使用完整 CSS 选择器的任何地方使用简写选择器:
$browser->type('@email', 'taylor@laravel.com');安装 Dusk 后,一个基础 Page 类将放置在你的 tests/Browser/Pages 目录中。此类别包含一个 siteElements 方法,可用于定义应在整个应用中每个页面上可用的全局简写选择器:
/**
* Get the global element shortcuts for the site.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}除了页面上定义的默认方法外,你还可以定义可在整个测试中使用的其他方法。例如,假设我们正在构建一个音乐管理应用。该应用某个页面的常见操作可能是创建播放列表。与其在每个测试中重写创建播放列表的逻辑,你可以在页面类上定义一个 createPlaylist 方法:
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;
class Dashboard extends Page
{
// Other page methods...
/**
* Create a new playlist.
*/
public function createPlaylist(Browser $browser, string $name): void
{
$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 命令。新组件放置在 tests/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.
*/
public function selector(): string
{
return '.date-picker';
}
/**
* Assert that the browser page contains the component.
*/
public function assert(Browser $browser): void
{
$browser->assertVisible($this->selector());
}
/**
* Get the element shortcuts for the component.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@date-field' => 'input.datepicker-input',
'@year-list' => 'div > div.datepicker-years',
'@month-list' => 'div > div.datepicker-months',
'@day-list' => 'div > div.datepicker-days',
];
}
/**
* Select the given date.
*/
public function selectDate(Browser $browser, int $year, int $month, int $day): void
{
$browser->click('@date-field')
->within('@year-list', function (Browser $browser) use ($year) {
$browser->click($year);
})
->within('@month-list', function (Browser $browser) use ($month) {
$browser->click($month);
})
->within('@day-list', function (Browser $browser) use ($day) {
$browser->click($day);
});
}
}定义组件后,我们可以轻松地从任何测试中选择日期选择器中的日期。而且,如果选择日期所需的逻辑发生变化,我们只需更新组件即可:
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
pest()->use(DatabaseMigrations::class);
test('basic example', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function (Browser $browser) {
$browser->selectDate(2019, 1, 30);
})
->assertSee('January');
});
});<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
/**
* A basic component test example.
*/
public function test_basic_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function (Browser $browser) {
$browser->selectDate(2019, 1, 30);
})
->assertSee('January');
});
}
}component 方法可用于检索限定在给定组件范围内的浏览器实例:
$datePicker = $browser->component(new DatePickerComponent);
$datePicker->selectDate(2019, 1, 30);
$datePicker->assertSee('January');[!WARNING]
大多数 Dusk 持续集成配置期望你的 Laravel 应用使用内置的 PHP 开发服务器在 8000 端口提供服务。因此,在继续之前,你应该确保你的持续集成环境的APP_URL环境变量值为http://127.0.0.1:8000。
要在 Heroku CI 上运行 Dusk 测试,请将以下 Google Chrome 构建包和脚本添加到你的 Heroku app.json 文件:
{
"environments": {
"test": {
"buildpacks": [
{ "url": "heroku/php" },
{ "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }
],
"scripts": {
"test-setup": "cp .env.testing .env",
"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"
}
}
}
}要在 Travis CI 上运行你的 Dusk 测试,请使用以下 .travis.yml 配置。由于 Travis CI 不是图形环境,我们需要采取一些额外步骤才能启动 Chrome 浏览器。此外,我们将使用 php artisan serve 来启动 PHP 的内置 Web 服务器:
language: php
php:
- 8.2
addons:
chrome: stable
install:
- cp .env.testing .env
- travis_retry composer install --no-interaction --prefer-dist
- php artisan key:generate
- php artisan dusk:chrome-driver
before_script:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
- php artisan serve --no-reload &
script:
- php artisan dusk如果你正在使用 GitHub Actions 运行你的 Dusk 测试,你可以使用以下配置文件作为起点。与 TravisCI 类似,我们将使用 php artisan serve 命令来启动 PHP 的内置 Web 服务器:
name: CI
on: [push]
jobs:
dusk-php:
runs-on: ubuntu-latest
env:
APP_URL: "http://127.0.0.1:8000"
DB_USERNAME: root
DB_PASSWORD: root
MAIL_MAILER: log
steps:
- uses: actions/checkout@v5
- name: Prepare The Environment
run: cp .env.example .env
- name: Create Database
run: |
sudo systemctl start mysql
mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
- name: Install Composer Dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Generate Application Key
run: php artisan key:generate
- name: Upgrade Chrome Driver
run: php artisan dusk:chrome-driver --detect
- name: Start Chrome Driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
- name: Run Laravel Server
run: php artisan serve --no-reload &
- name: Run Dusk Tests
run: php artisan dusk
- name: Upload Screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tests/Browser/screenshots
- name: Upload Console Logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: console
path: tests/Browser/console如果你正在使用 Chipper CI 运行你的 Dusk 测试,你可以使用以下配置文件作为起点。我们将使用 PHP 的内置服务器来运行 Laravel,以便我们可以监听请求:
# file .chipperci.yml
version: 1
environment:
php: 8.2
node: 16
# Include Chrome in the build environment
services:
- dusk
# Build all commits
on:
push:
branches: .*
pipeline:
- name: Setup
cmd: |
cp -v .env.example .env
composer install --no-interaction --prefer-dist --optimize-autoloader
php artisan key:generate
# Create a dusk env file, ensuring APP_URL uses BUILD_HOST
cp -v .env .env.dusk.ci
sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
- name: Compile Assets
cmd: |
npm ci --no-audit
npm run build
- name: Browser Tests
cmd: |
php -S [::0]:8000 -t public 2>server.log &
sleep 2
php artisan dusk:chrome-driver $CHROME_DRIVER
php artisan dusk --env=ci要了解更多关于在 Chipper CI 上运行 Dusk 测试的信息,包括如何使用数据库,请查阅 官方 Chipper CI 文档。