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 驱动程序二进制文件。
php artisan dusk:install
接下来,在应用程序的 .env
文件中设置 APP_URL
环境变量。此值应与你在浏览器中访问应用程序时使用的 URL 匹配。
如果你使用 Laravel Sail 管理你的本地开发环境,请参阅 Sail 文档中关于 配置和运行 Dusk 测试 的内容。
管理 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
特性。RefreshDatabase
特性利用数据库事务,而这些事务在 HTTP 请求中不适用或不可用。相反,你有两个选项:DatabaseMigrations
特性和 DatabaseTruncation
特性。
使用数据库迁移
DatabaseMigrations
特性将在每个测试之前运行你的数据库迁移。但是,对于每个测试都删除和重新创建数据库表通常比截断表慢。
<?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
特性将在第一个测试中迁移你的数据库,以确保你的数据库表已正确创建。但是,在随后的测试中,数据库的表将简单地被截断——与重新运行所有数据库迁移相比,这可以提高速度。
<?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; //}
默认情况下,此特性将截断除 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
方法
// 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 提供了多种方法来与表单和输入元素进行交互。首先,让我们看看一个将文本键入输入字段的示例
请注意,尽管该方法在必要时接受一个,但我们不需要将 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 选择器匹配的元素显示在页面上。默认情况下,这将暂停测试最多 5 秒,然后抛出异常。如果需要,您可以将自定义超时阈值作为方法的第二个参数传递。
// Wait a maximum of five seconds for the selector...$browser->waitFor('.selector'); // Wait a maximum of one second for the selector...$browser->waitFor('.selector', 1);
您还可以等到与给定选择器匹配的元素包含给定的文本。
// 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 构建包和脚本添加到您的 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 文档。