Laravel Dusk
简介
Laravel Dusk 提供了一个表达力强、易于使用的浏览器自动化和测试 API。默认情况下,Dusk 不需要您在本地计算机上安装 JDK 或 Selenium。相反,Dusk 使用独立的 ChromeDriver 安装。但是,您可以随意使用您希望的任何其他与 Selenium 兼容的驱动程序。
安装
要开始使用,您应该安装 Google Chrome 并将 laravel/dusk
Composer 依赖项添加到您的项目中
composer require laravel/dusk --dev
如果您正在手动注册 Dusk 的服务提供者,则 绝不 应在生产环境中注册它,因为这样做可能导致任意用户能够使用您的应用程序进行身份验证。
安装 Dusk 包后,执行 dusk:install
Artisan 命令。dusk:install
命令将创建一个 tests/Browser
目录、一个示例 Dusk 测试,并为您的操作系统安装 Chrome Driver 二进制文件
php artisan dusk:install
接下来,在应用程序的 .env
文件中设置 APP_URL
环境变量。此值应与您在浏览器中访问应用程序时使用的 URL 匹配。
如果您使用 Laravel Sail 来管理您的本地开发环境,请同时查阅关于 配置和运行 Dusk 测试 的 Sail 文档。
管理 ChromeDriver 安装
如果您想安装与 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
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( 'https://127.0.0.1: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; uses(DatabaseMigrations::class); //
<?php namespace Tests\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations;use Laravel\Dusk\Browser;use Tests\DuskTestCase; class ExampleTest extends DuskTestCase{ use DatabaseMigrations; //}
执行 Dusk 测试时,不能使用 SQLite 内存数据库。由于浏览器在其自己的进程中执行,因此它将无法访问其他进程的内存数据库。
使用数据库截断
DatabaseTruncation
trait 将在第一次测试时迁移您的数据库,以确保已正确创建数据库表。但是,在后续测试中,数据库的表将简单地被截断 - 与重新运行所有数据库迁移相比,这提供了速度上的提升
<?php use Illuminate\Foundation\Testing\DatabaseTruncation;use Laravel\Dusk\Browser; uses(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
属性
如果您使用 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:fails
dusk
命令接受 Pest / PHPUnit 测试运行器通常接受的任何参数,例如允许你只运行给定组的测试。
php artisan dusk --group=foo
如果你使用 Laravel Sail 来管理本地开发环境,请查阅 Sail 文档中关于配置和运行 Dusk 测试的部分。
手动启动 ChromeDriver
默认情况下,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( 'https://127.0.0.1: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; uses(DatabaseMigrations::class); test('basic example', function () { $user = User::factory()->create([ ]); $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([ ]); $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');});
在使用 loginAs
方法后,用户会话将在该文件中的所有测试中保持。
Cookie
你可以使用 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');
执行 JavaScript
你可以使用 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');
与元素交互
Dusk 选择器
为与元素交互选择好的 CSS 选择器是编写 Dusk 测试中最困难的部分之一。随着时间的推移,前端更改可能会导致像下面这样的 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');
如果需要,你可以通过 selectorHtmlAttribute
方法自定义 Dusk 选择器使用的 HTML 属性。通常,应该从应用程序的 AppServiceProvider
的 boot
方法中调用此方法。
use Laravel\Dusk\Dusk; Dusk::selectorHtmlAttribute('data-dusk');
文本、值和属性
检索和设置值
Dusk 提供了几种方法来与页面上元素的当前值、显示文本和属性交互。例如,要获取与给定 CSS 或 Dusk 选择器匹配的元素的“value”,请使用 value
方法。
// Retrieve the value...$value = $browser->value('selector'); // Set the value...$browser->value('selector', 'value');
你可以使用 inputValue
方法来获取具有给定字段名称的输入元素的“value”。
$value = $browser->inputValue('field');
检索文本
text
方法可用于检索与给定选择器匹配的元素的显示文本。
$text = $browser->text('selector');
检索属性
最后,attribute
方法可用于检索与给定选择器匹配的元素的属性值。
$attribute = $browser->attribute('selector', 'value');
与表单交互
键入值
Dusk 提供了多种方法来与表单和输入元素交互。首先,让我们看一下将文本键入输入字段的示例。
请注意,虽然该方法接受一个(如果需要),但我们不需要将 CSS 选择器传递到 type
方法中。如果未提供 CSS 选择器,Dusk 将搜索具有给定 name
属性的 input
或 textarea
字段。
要将文本附加到字段而不清除其内容,可以使用 append
方法。
$browser->type('tags', 'foo') ->append('tags', ', bar, baz');
你可以使用 clear
方法清除输入的值。
$browser->clear('email');
你可以指示 Dusk 使用 typeSlowly
方法缓慢键入。默认情况下,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');
attach 函数要求在你的服务器上安装并启用 Zip
PHP 扩展。
按下按钮
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)) { // ...}
这些方法与 jQuery 交互。如果页面上没有 jQuery,Dusk 将自动将其注入到页面中,使其在测试期间可用。
使用键盘
keys
方法允许你向给定元素提供比 type
方法通常允许的更复杂的输入序列。例如,你可以指示 Dusk 在输入值时按住修饰键。 在此示例中,在将 taylor
输入到匹配给定选择器的元素时,将按住 shift
键。 在键入 taylor
后,将键入 swift
,而无需任何修饰键
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
keys
方法的另一个有价值的用例是将“键盘快捷键”组合发送到应用程序的主 CSS 选择器
$browser->keys('.app', ['{command}', 'j']);
所有修饰键(如 {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);
JavaScript 对话框
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 表达式
有时,你可能希望暂停测试的执行,直到给定的 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);
等待 Vue 表达式
可以使用 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');
等待 JavaScript 事件
可以使用 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 assertScript assertSourceHas assertSourceMissing assertSeeLink assertDontSeeLink assertInputValue assertInputValueIsNot assertChecked assertNotChecked assertIndeterminate assertRadioSelected assertRadioNotSelected assertSelected assertNotSelected assertSelectHasOptions assertSelectMissingOptions assertSelectHasOption assertSelectMissingOption assertValue assertValueIsNot assertAttribute assertAttributeContains assertAttributeDoesntContain assertAriaAttribute assertDataAttribute assertVisible assertPresent assertNotPresent assertMissing assertInputPresent assertInputMissing assertDialogOpened assertEnabled assertDisabled assertButtonEnabled assertButtonDisabled assertFocused assertNotFocused assertAuthenticated assertGuest assertAuthenticatedAs assertVue assertVueIsNot assertVueContains assertVueDoesntContain
assertTitle
断言页面标题与给定的文本匹配
$browser->assertTitle($title);
assertTitleContains
断言页面标题包含给定的文本
$browser->assertTitleContains($title);
assertUrlIs
断言当前 URL(不包括查询字符串)与给定的字符串匹配
$browser->assertUrlIs($url);
assertSchemeIs
断言当前 URL 方案与给定的方案匹配
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
断言当前 URL 协议与给定的协议不匹配
$browser->assertSchemeIsNot($scheme);
assertHostIs
断言当前 URL 主机与给定的主机匹配
$browser->assertHostIs($host);
assertHostIsNot
断言当前 URL 主机与给定的主机不匹配
$browser->assertHostIsNot($host);
assertPortIs
断言当前 URL 端口与给定的端口匹配
$browser->assertPortIs($port);
assertPortIsNot
断言当前 URL 端口与给定的端口不匹配
$browser->assertPortIsNot($port);
assertPathBeginsWith
断言当前 URL 路径以给定的路径开头
$browser->assertPathBeginsWith('/home');
assertPathEndsWith
断言当前 URL 路径以给定的路径结尾
$browser->assertPathEndsWith('/home');
assertPathContains
断言当前 URL 路径包含给定的路径
$browser->assertPathContains('/home');
assertPathIs
断言当前路径与给定的路径匹配
$browser->assertPathIs('/home');
assertPathIsNot
断言当前路径与给定的路径不匹配
$browser->assertPathIsNot('/home');
assertRouteIs
断言当前 URL 与给定命名路由的 URL 匹配
$browser->assertRouteIs($name, $parameters);
assertQueryStringHas
断言给定的查询字符串参数存在
$browser->assertQueryStringHas($name);
断言给定的查询字符串参数存在并且具有给定的值
$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
断言给定的查询字符串参数不存在
$browser->assertQueryStringMissing($name);
assertFragmentIs
断言 URL 当前的哈希片段与给定的片段匹配
$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
断言 URL 当前的哈希片段以给定的片段开头
$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
断言 URL 当前的哈希片段与给定的片段不匹配
$browser->assertFragmentIsNot('anchor');
assertHasCookie
断言给定的加密 Cookie 存在
$browser->assertHasCookie($name);
assertHasPlainCookie
断言给定的未加密 Cookie 存在
$browser->assertHasPlainCookie($name);
assertCookieMissing
断言给定的加密 Cookie 不存在
$browser->assertCookieMissing($name);
assertPlainCookieMissing
断言给定的未加密 Cookie 不存在
$browser->assertPlainCookieMissing($name);
assertCookieValue
断言加密的 Cookie 具有给定的值
$browser->assertCookieValue($name, $value);
assertPlainCookieValue
断言未加密的 Cookie 具有给定的值
$browser->assertPlainCookieValue($name, $value);
assertSee
断言给定的文本在页面上存在
$browser->assertSee($text);
assertDontSee
断言给定的文本在页面上不存在
$browser->assertDontSee($text);
assertSeeIn
断言给定的文本在选择器中存在
$browser->assertSeeIn($selector, $text);
assertDontSeeIn
断言给定的文本在选择器中不存在
$browser->assertDontSeeIn($selector, $text);
assertSeeAnythingIn
断言任何文本在选择器中存在
$browser->assertSeeAnythingIn($selector);
assertSeeNothingIn
断言没有文本在选择器中存在
$browser->assertSeeNothingIn($selector);
assertScript
断言给定的 JavaScript 表达式求值为给定的值
$browser->assertScript('window.isLoaded') ->assertScript('document.readyState', 'complete');
assertSourceHas
断言给定的源代码在页面上存在
$browser->assertSourceHas($code);
assertSourceMissing
断言给定的源代码在页面上不存在
$browser->assertSourceMissing($code);
assertSeeLink
断言给定的链接在页面上存在
$browser->assertSeeLink($linkText);
assertDontSeeLink
断言给定的链接在页面上不存在
$browser->assertDontSeeLink($linkText);
assertInputValue
断言给定的输入字段具有给定的值
$browser->assertInputValue($field, $value);
assertInputValueIsNot
断言给定的输入字段不具有给定的值
$browser->assertInputValueIsNot($field, $value);
assertChecked
断言给定的复选框被选中
$browser->assertChecked($field);
assertNotChecked
断言给定的复选框未被选中
$browser->assertNotChecked($field);
assertIndeterminate
断言给定的复选框处于不确定状态
$browser->assertIndeterminate($field);
assertRadioSelected
断言给定的单选字段被选中
$browser->assertRadioSelected($field, $value);
assertRadioNotSelected
断言给定的单选字段未被选中
$browser->assertRadioNotSelected($field, $value);
assertSelected
断言给定的下拉列表选中了给定的值
$browser->assertSelected($field, $value);
assertNotSelected
断言给定的下拉列表未选中给定的值
$browser->assertNotSelected($field, $value);
assertSelectHasOptions
断言给定的值数组可供选择
$browser->assertSelectHasOptions($field, $values);
assertSelectMissingOptions
断言给定的值数组不可供选择
$browser->assertSelectMissingOptions($field, $values);
assertSelectHasOption
断言给定的值在给定的字段上可供选择
$browser->assertSelectHasOption($field, $value);
assertSelectMissingOption
断言给定的值不可供选择
$browser->assertSelectMissingOption($field, $value);
assertValue
断言与给定选择器匹配的元素具有给定的值
$browser->assertValue($selector, $value);
assertValueIsNot
断言与给定选择器匹配的元素不具有给定的值
$browser->assertValueIsNot($selector, $value);
assertAttribute
断言与给定选择器匹配的元素在提供的属性中具有给定的值
$browser->assertAttribute($selector, $attribute, $value);
assertAttributeContains
断言与给定选择器匹配的元素在提供的属性中包含给定的值
$browser->assertAttributeContains($selector, $attribute, $value);
assertAttributeDoesntContain
断言与给定选择器匹配的元素在提供的属性中不包含给定的值
$browser->assertAttributeDoesntContain($selector, $attribute, $value);
assertAriaAttribute
断言与给定选择器匹配的元素在提供的 aria 属性中具有给定的值
$browser->assertAriaAttribute($selector, $attribute, $value);
例如,给定标记 <button aria-label="Add"></button>
,您可以像这样断言 aria-label
属性
$browser->assertAriaAttribute('button', 'label', 'Add')
assertDataAttribute
断言与给定选择器匹配的元素在提供的 data 属性中具有给定的值
$browser->assertDataAttribute($selector, $attribute, $value);
例如,给定标记 <tr id="row-1" data-content="attendees"></tr>
,您可以像这样断言 data-label
属性
$browser->assertDataAttribute('#row-1', 'content', 'attendees')
assertVisible
断言与给定选择器匹配的元素是可见的
$browser->assertVisible($selector);
assertPresent
断言与给定选择器匹配的元素在源代码中存在
$browser->assertPresent($selector);
assertNotPresent
断言与给定选择器匹配的元素在源代码中不存在
$browser->assertNotPresent($selector);
assertMissing
断言与给定选择器匹配的元素是不可见的
$browser->assertMissing($selector);
assertInputPresent
断言具有给定名称的输入存在
$browser->assertInputPresent($name);
assertInputMissing
断言在源代码中不存在具有给定名称的输入
$browser->assertInputMissing($name);
assertDialogOpened
断言已打开具有给定消息的 JavaScript 对话框
$browser->assertDialogOpened($message);
assertEnabled
断言给定的字段已启用
$browser->assertEnabled($field);
assertDisabled
断言给定的字段已禁用
$browser->assertDisabled($field);
assertButtonEnabled
断言给定的按钮已启用
$browser->assertButtonEnabled($button);
assertButtonDisabled
断言给定的按钮已禁用
$browser->assertButtonDisabled($button);
assertFocused
断言给定的字段已聚焦
$browser->assertFocused($field);
assertNotFocused
断言给定的字段未聚焦
$browser->assertNotFocused($field);
assertAuthenticated
断言用户已通过身份验证
$browser->assertAuthenticated();
assertGuest
断言用户未通过身份验证
$browser->assertGuest();
assertAuthenticatedAs
断言用户已通过身份验证为给定的用户
$browser->assertAuthenticatedAs($user);
assertVue
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'); });}
assertVueIsNot
断言给定的 Vue 组件数据属性与给定的值不匹配
$browser->assertVueIsNot($property, $value, $componentSelector = null);
assertVueContains
断言给定的 Vue 组件数据属性是一个数组,并且包含给定的值
$browser->assertVueContains($property, $value, $componentSelector = null);
assertVueDoesntContain
断言给定的 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 选择器的任何位置使用速记选择器
全局速记选择器
安装 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; uses(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'); }); }}
持续集成
大多数 Dusk 持续集成配置都期望您的 Laravel 应用程序使用内置的 PHP 开发服务器在端口 8000 上提供服务。因此,在继续之前,您应该确保您的持续集成环境的 APP_URL
环境变量值为 http://127.0.0.1:8000
。
Heroku CI
要在 Heroku CI 上运行 Dusk 测试,请将以下 Google Chrome buildpack 和脚本添加到您的 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
要在 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 https://127.0.0.1 & - php artisan serve --no-reload & script: - php artisan dusk
GitHub Actions
如果您使用 GitHub Actions 运行 Dusk 测试,您可以将以下配置文件作为起点。与 TravisCI 类似,我们将使用 php artisan serve
命令启动 PHP 内置的 Web 服务器。
name: CIon: [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@v4 - 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
如果您使用 Chipper CI 运行 Dusk 测试,您可以将以下配置文件作为起点。我们将使用 PHP 的内置服务器来运行 Laravel,以便监听请求。
# file .chipperci.ymlversion: 1 environment: php: 8.2 node: 16 # Include Chrome in the build environmentservices: - dusk # Build all commitson: 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 官方文档。