Dusk 浏览器测试
Laravel Dusk
介绍
Laravel Dusk 提供了富有表现力、简单易用的浏览器自动化及测试 API 。默认情况下,Dusk 不需要在你的机器上安装 JDK 或者 Selenium 。而是需要使用单独的 ChromeDriver 进行安装。当然,你也可以自由使用其他的兼容 Selenium 的驱动程序。
安装
你应该先向你的项目中添加 laravel/dusk
Composer 依赖 :
composer require --dev laravel/dusk
注意:如果你是手动注册 Dusk 服务提供者,一定 不能 在你的生产环境中注册,这样可能会导致一些不守规矩的用户拥有控制你应用的权限。
安装好 Dusk 包后,运行 dusk:install
命令:
php artisan dusk:install
Browser
目录将会在 tests
目录下被创建,并且包含一个测试用例。接下来,在你的 .env
文件中设置 APP_URL
变量。这个值应该与你在浏览器中打开本应用的 URL 相匹配。
要运行测试,使用 dusk
命令。 dusk
命令可以使用与 phpunit
命令同样的参数:
php artisan dusk
如果上次运行 Dusk 命令时测试失败,则可以通过使用 dusk:fails
命令重新运行失败的测试来节省时间:
php artisan dusk:fails
管理 ChromeDriver 安装
如果你想安装与 Laravel Dusk 附带版本不同的 ChromeDriver,可以使用 dusk:chrome driver
命令:
# 为你的操作系统安装最新版本的 ChromeDriver...
php artisan dusk:chrome-driver
# 为你的操作系统安装指定版本的 ChromeDriver...
php artisan dusk:chrome-driver 74
# 为所有支持的操作系统安装指定版本的 ChromeDriver...
php artisan dusk:chrome-driver --all
注意:Dusk 要求
ChromeDriver
二进制文件是可执行的。如果在 Dusk 运行时遇到问题,可以使用以下命令确保二进制文件是可执行的:chmod -R 0755 vendor/laravel/dusk/bin
。
使用其他浏览器
默认情况下, Dusk 使用 Google Chrome 浏览器和一个单独安装的 ChromeDriver 来运行你的浏览器测试。当然,你可以运行你自己的 Selenium 服务,用任何你想用的浏览器来进行测试。
如果要这么做,打开 tests/DuskTestCase.php
文件,这个是应用测试用例的基类。在这个文件中,你可以移除对 startChromeDriver
方法的调用。这样 Dusk 就不会自动启动 ChromeDriver 了。
/**
* 准备执行 Dusk 测试。
*
* @beforeClass
* @return void
*/
public static function prepare()
{
// static::startChromeDriver();
}
然后,你可以按照自己的意愿修改 driver
方法来连接到你选定的 URL 和端口。此外,你可以修改 「desired capabilities」(期望能力),它将会被传递给 WebDriver:
/**
* 创建 RemoteWebDriver 实例。
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}
开始
创建测试
要创建一个 Dusk 测试,可以使用 dusk:make
命令。创建的测试将会被放在 tests/Browser
目录中:
php artisan dusk:make LoginTest
运行测试
使用 dusk
Artisan 命令来运行你的浏览器测试:
php artisan dusk
如果上次运行 dusk 命令时测试失败,则可以通过使用 dusk:fails
命令重新运行失败的测试来节省时间:
php artisan dusk:fails
dusk
命令接受任何能让 PHPUnit 正常运行的参数。例如,让你可以在指定 group 中运行测试:
php artisan dusk --group=foo
手动运行 ChromeDriver
默认情况下,Dusk 会尝试自动运行 ChromeDriver。如果你在特定的系统中不能运行,可以在运行 dusk
命令前通过手动的方式来运行 ChromeDriver。 如果你选择手动运行 ChromeDriver,你需要在你的 tests/DuskTestCase.php
文件中注释掉下面这一行:
/**
* 为 Dusk 测试做准备。
*
* @beforeClass
* @return void
*/
public static function prepare()
{
// static::startChromeDriver();
}
另外, 如果你的 ChromeDriver 运行在不是 9515 的其他端口 ,你需要修改同一个类中的 driver 方法:
/**
* 创建 RemoteWebDriver 实例。
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
环境处理
为了让 Dusk 使用自己的环境文件来运行测试,你需要在项目根目录创建一个 .env.dusk.{environment}
文件。例如,你想用 local
环境来运行 dusk
命令,你需要创建一个 .env.dusk.local
文件。
运行测试的时候,Dusk 会备份你的 .env
文件并且重命名你的 Dusk 环境文件为 .env
。当测试结束后,它会恢复你的 .env
文件。
创建浏览器
首先让我们来写一个测试用例,这个例子可以验证我们是否能够登录系统。生成测试用例之后,我们可以对它稍作调整并让它可以跳转到登录界面,输入登录信息之后,点击「登录」按钮。我们通过 browse
方法来创建一个浏览器实例:
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* 一个基本的浏览器测试示例
*
* @return void
*/
public function testBasicExample()
{
$user = factory(User::class)->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
}
}
如上例所示,browse
方法接收了一个回调参数。Dusk 会自动将这个浏览器实例注入到回调过程中,而且这个浏览器实例可以和你的应用进行交互和断言。
创建多个浏览器
有时候你可能需要多个浏览器才能正确的进行测试。例如,使用多个浏览器测试通过 websockets 进行通讯的在线聊天页面。想要创建多个浏览器,需要在 browse
方法的回调中,用名字来区分浏览器实例,然后传给回调去「申请」多个浏览器实例:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home')
->waitForText('Message');
$second->loginAs(User::find(2))
->visit('/home')
->waitForText('Message')
->type('message', 'Hey Taylor')
->press('Send');
$first->waitForText('Hey Taylor')
->assertSee('Jeffrey Way');
});
改变浏览器窗口大小
你可以使用 resize
方法去调整浏览器的窗口大小:
$browser->resize(1920, 1080);
maximize
方法可以将浏览器窗口最大化:
$browser->maximize();
fitContent
方法将自动适配浏览器的窗口大小和页面内容的尺寸:
$browser->fitContent();
测试失败时,Dusk 会自动将浏览器窗口缩放至内容大小并拍下屏幕快照,你可以通过调用 disableFitOnFailure
方法来禁用这一特性:
$browser->disableFitOnFailure();
你可以使用 move
方法将浏览器窗口移动到屏幕上的其他位置:
$browser->move(100, 100);
浏览器宏
如果你想定义一个可以在各种测试中重复使用的自定义浏览器方法,可以在 Browser
类中使用 macro
方法。通常,你应该从 服务提供者 的 boot
方法中调用它:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;
class DuskServiceProvider extends ServiceProvider
{
/**
* 注册Dusk的浏览器宏。
*
* @return void
*/
public function boot()
{
Browser::macro('scrollToElement', function ($element = null) {
$this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
return $this;
});
}
}
macro
函数接收方法名作为其第一个参数,并接收闭包作为其第二个参数。 将宏作为 Browser
实现上的方法调用宏时,将执行宏的闭包:
$this->browse(function ($browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#credit-card-details')
->assertSee('Enter Credit Card Details');
});
用户认证
我们经常会测试需要身份验证的页面,你可以使用 Dusk 的 loginAs
方法来避免在每次测试期间与登录页面进行交互。 loginAs
方法接收用户 ID 或者用户模型实例:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
注意:使用
loginAs
方法后,用户会话在文件中的所有测试被维护。
数据库迁移
当你的测试需要迁移时,例如上面的身份验证示例,不能用 RefreshDatabase
trait。 RefreshDatabase
特征利用了数据库事务,该事务不适用于HTTP请求。 而是使用 DatabaseMigrations
trait :
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
}
Cookies
你可以使用 cookie
方法来获取或者设置加密过的 cookie 的值:
$browser->cookie('name');
$browser->cookie('name', 'Taylor');
使用 plainCookie
则可以获取或者设置未加密过的 cookie 的值:
$browser->plainCookie('name');
$browser->plainCookie('name', 'Taylor');
你可以使用 deleteCookie
方法删除指定的 cookie:
$browser->deleteCookie('name');
获取截图
你可以使用 screenshot
方法来截图并将其指定文件名存储,所有截图都将存放在 tests/Browser/screenshots
目录下:
$browser->screenshot('filename');
控制台输出结果保存到硬盘
你可以使用 storeConsoleLog
方法将控制台输出指定文件名并写入磁盘,控制台输出默认存放在 tests/Browser/console
目录下:
$browser->storeConsoleLog('filename');
页面源码保存到硬盘
你可以使用 storeSource
方法将页面当前源代码指定文件名并写入磁盘,页面源代码默认会存放到 tests/Browser/source
目录:
$browser->storeSource('filename');
与元素交互
Dusk 选择器
编写 Dusk 测试最困难的部分之一就是选择良好的 CSS 选择器与元素进行交互。 随着时间的推移,前端的更改可能会导致如下所示的 CSS 选择器无法通过测试:
// HTML...
<button>Login</button>
// 测试...
$browser->click('.login-page .container div > button');
Dusk 选择器可以让你专注于编写有效的测试,而不必记住 CSS 选择器。要定义一个选择器,你需要添加一个 dusk
属性在 HTML 元素中。然后在选择器前面加上 @
用来在 Dusk 测试中操作元素:
// HTML...
<button dusk="login-button">Login</button>
// 测试...
$browser->click('@login-button');
点击链接
要点击链接,可以在浏览器实例下使用 clickLink
方法。 clickLink
方法将点击指定文本的链接:
$browser->clickLink($linkText);
您可以使用 seeLink
方法来确定具有给定显示文本的链接在页面上是否可见:
if ($browser->seeLink($linkText)) {
// ...
}
注意:这些方法与 jQuery 交互。 如果页面上没有 jQuery , Dusk 会自动将其注入到页面中,以便在测试期间可用。
文本、值 & 属性
获取 & 设置值
Dusk 提供了多个方法用于和页面元素的当前显示文本、值和属性进行交互,例如,要获取匹配指定选择器的元素的「值」,使用 value
方法:
// 获取值...
$value = $browser->value('selector');
// 设置值...
$browser->value('selector', 'value');
你可以使用 inputValue
方法来获取包含指定字段名称的输入元素的「值」:
// 获取输入元素的值...
$inputValue = $browser->inputValue('field');
获取文本
text
方法可以用于获取匹配指定选择器元素文本:
$text = $browser->text('selector');
获取属性
最后,attribute
方法可以用于获取匹配指定选择器元素属性:
$attribute = $browser->attribute('selector', 'value');
使用表单
输入值
Dusk提供了多种方法来与表单和输入元素进行交互。首先,让我们看一个在字段中输入值的示例:
$browser->type('email', 'taylor@laravel.com');
注意,尽管该方法在需要时接收,但是我们不需要将 CSS 选择器传递给 type
方法。如果没有提供 CSS 选择器,Dusk 会搜索包含指定 name
属性的输入字段,最后,Dusk 会尝试查找包含指定 name
属性的 textarea
。
要想将文本附加到一个字段之后而且不清除其内容, 你可以使用 append
方法:
$browser->type('tags', 'foo')
->append('tags', ', bar, baz');
你可以使用 clear 方法清除输入值:
$browser->clear('email');
你可以使用typeSlowly
方法指示 Dusk 缓慢键入。 默认情况下,Dusk 在两次按键之间将暂停100毫秒。 要自定义按键之间的时间量,你可以将适当的毫秒数作为方法的第二个参数传递:
$browser->typeSlowly('mobile', '+1 (202) 555-5555');
$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
你可以使用 appendSlowly
方法缓慢添加文本:
$browser->type('tags', 'foo')
->appendSlowly('tags', ', bar, baz');
下拉菜单
需要在下拉菜单中选择值,你可以使用 select
方法。 类似于 type
方法, select
方法并不是一定要传入 CSS 选择器。 当使用 select
方法时,你应该传递选项实际的值而不是它的显示文本:
$browser->select('size', 'Large');
你也可以通过省略第二个参数来随机选择一个选项:
$browser->select('size');
复选框
使用「check」 复选框时,你可以使用 check
方法。 像其他许多与input 相关的方法,并不是必须传入 CSS 选择器。 如果准确的选择器无法找到的时候,Dusk 会搜索能够与 name
属性匹配的复选框:
$browser->check('terms');
$browser->uncheck('terms');
单选按钮
使用 「select」中单选按钮选项时,你可以使用 radio
这个方法。 像很多其他的与输入相关的方法一样, 它也并不是必须传入 CSS 选择器。如果准确的选择器无法被找到的时候, Dusk 会搜索能够与 name
属性或者 value
属性相匹配的单选按钮:
$browser->radio('version', 'php7');
附件
attach
方法可以附加一个文件到 file
input 元素中。 像很多其他的与输入相关的方法一样,他也并不是必须传入CSS 选择器。如果准确的选择器没有被找到的时候, Dusk 会搜索与 name
属性匹配的文件输入框:
$browser->attach('photo', __DIR__.'/photos/me.png');
注意:attach 方法需要使用 PHP
Zip
扩展,你的服务器必须安装了此扩展。
使用键盘
keys
方法让你可以再指定元素中输入比 type
方法更加复杂的输入序列。例如,你可以在输入值的同时按下按键。在这个例子中, 输入 taylor
时, shift
键也同时被按下。当 taylor
输入完之后, 将会输入 otwell
而不会按下任何按键:
$browser->keys('selector', ['{shift}', 'taylor'], 'otwell');
你甚至可以在你的应用中选中某个元素之后按下「快捷键」:
$browser->keys('.app', ['{command}', 'j']);
技巧:所有包在
{}
中的键盘按键,都对应定义于Facebook\WebDriver\WebDriverKeys
类中, 你可以在 GitHub中找到。
使用鼠标
点击元素
click
方法可用于「点击」与给定选择器匹配的元素:
$browser->click('.selector');
clickAtXPath
方法可用于「单击」与给定 XPath 表达式匹配的元素:
$browser->clickAtXPath('//div[@class = "selector"]');
clickAtPoint
方法可用于「点击」相对于浏览器可视区域的给定坐标对上的最高元素:
$browser->clickAtPoint(0, 0);
doubleClick
方法可用于模拟鼠标的双击:
$browser->doubleClick();
rightClick
方法可用于模拟鼠标的右击:
$browser->rightClick();
$browser->rightClick('.selector');
clickAndHold
方法可用于模拟被单击并按住的鼠标按钮。 随后调用releaseMouse
方法将撤消此行为并释放鼠标按钮:
$browser->clickAndHold()
->pause(1000)
->releaseMouse();
鼠标悬停
mouseover
方法可用于与给定选择器匹配的元素的鼠标悬停动作:
$browser->mouseover('.selector');
拖放
drag
方法用于将与指定选择器匹配的元素拖到其它元素:
$browser->drag('.from-selector', '.to-selector');
或者,可以在单一方向上拖动元素:
$browser->dragLeft('.selector', 10);
$browser->dragRight('.selector', 10);
$browser->dragUp('.selector', 10);
$browser->dragDown('.selector', 10);
最后,你可以将元素拖动给定的偏移量:
$browser->dragOffset('.selector', 10, 10);
JavaScript 对话框
Dusk 提供了几种与 JavaScript 对话框交互的方法:
// 等待对话框显示:
$browser->waitForDialog($seconds = null);
// 断言对话框已经显示,并且其消息与给定值匹配:
$browser->assertDialogOpened('value');
// 在打开的 JavaScript 提示对话框中输入给定值:
$browser->typeInDialog('Hello World');
通过点击确定按钮关闭打开的 JavaScript 对话框:
$browser->acceptDialog();
通过点击取消按钮关闭打开的 JavaScript 对话框(仅对确认对话框有效):
$browser->dismissDialog();
选择器作用范围
有时可能希望在给定的选择器范围内执行多个操作。比如,可能想要断言表格中存在某些文本,然后点击表格中的一个按钮。可以使用 with
方法实现此需求。回调函数内所有被执行的操作都被限定在原始的选择器上:
有时,您可能希望在给定选择器内确定所有操作范围的同时执行多项操作。 例如,您可能希望断言某些文本仅存在于表中,然后单击该表中的按钮。 您可以使用“ with”方法来完成此操作。 在给with
方法的回调中执行的所有操作都将限于原始选择器:
$browser->with('.table', function ($table) {
$table->assertSee('Hello World')
->clickLink('Delete');
});
你可能偶尔需要在当前范围之外执行断言。 你可以使用 elsewhere
方法来完成此操作:
$browser->with('.table', function ($table) {
// Current scope is `body .table`...
$browser->elsewhere('.page-title', function ($title) {
// Current scope is `body .page-title`...
$title->assertSee('Hello World');
});
});
等待元素
在测试大面积使用 JavaScript 的应用时,在进行测试之前,经常需要「等待」指定元素或数据可用。Dusk 使之更容易。使用一系列方法,可以等到页面元素可用,甚至给定的 JavaScript 表达式执行结果为 true
。
等待
如果需要测试暂停指定的毫秒数,可以使用 pause
方法:
$browser->pause(1000);
等待选择器
waitFor
方法可以用于暂停执行测试,直到页面上与给定 CSS 选择器匹配的元素被显示。默认情况下,将在暂停超过 5 秒后抛出异常。如果有必要,可以传递自定义超时时长作为其第二个参数:
// 等待选择器 5 秒时间...
$browser->waitFor('.selector');
// 等待选择器 1 秒时间...
$browser->waitFor('.selector', 1);
你也可以等待指定选择器从页面消失:
$browser->waitUntilMissing('.selector');
$browser->waitUntilMissing('.selector', 1);
选择器可用时限定作用域范围
有时, 你或许希望等待给定选择器, 然后与匹配选择器的元素进行交互。例如,你可能希望等到模态窗口可用,然后在模态窗口中点击「确定」按钮。在这种情况下,可以使用 whenAvailable
方法。给定回调内的所有要执行的元素操作都将被限定在起始选择器上:
$browser->whenAvailable('.modal', function ($modal) {
$modal->assertSee('Hello World')
->press('OK');
});
等待文本
waitForText
方法可以用于等待页面上给定文字被显示:
// 等待指定文本删除 5 秒时间...
$browser->waitForText('Hello World');
// 等待指定文本删除 1 秒时间...
$browser->waitForText('Hello World', 1);
你可以使用 waitUntilMissingText
方法来等待,直到显示的文本已从页面中删除为止:
// 等待指定文本删除 5 秒时间...
$browser->waitUntilMissingText('Hello World');
// 等待指定文本删除 1 秒时间...
$browser->waitUntilMissingText('Hello World', 1);
等待链接
waitForLink
方法用于等待给定链接文字在页面上显示:
// 等待指定链接 5 秒时间...
$browser->waitForLink('Create');
// 等待指定链接 1 秒时间...
$browser->waitForLink('Create', 1);
等待页面跳转
在给出类似 $browser->assertPathIs('/home')
路径断言时, 如果 window.location.pathname
被异步更新,断言就会失败。可以使用 waitForLocation
方法等待页面跳转到给定路径:
$browser->waitForLocation('/secret');
还可以等待被命名的路由跳转:
$browser->waitForRoute($routeName, $parameters);
等待页面重新加载
如果要在页面重新加载后断言,可以使用 waitForReload
方法:
$browser->click('.some-action')
->waitForReload()
->assertSee('something');
等待 JavaScript 表达式
有时会希望暂停执行测试,直到给定的 JavaScript 表达式执行结果为 true
。可以使用 waitUntil
方法轻易地达成此目的。传送一个表达式给此方法,不需要包含 return
关键字或者结束分号:
// 等待表达式为 true 5 秒时间...
$browser->waitUntil('App.dataLoaded');
$browser->waitUntil('App.data.servers.length > 0');
// 等待表达式为 true 1 秒时间...
$browser->waitUntil('App.data.servers.length > 0', 1);
等待 Vue 表达式
下述方法可用于一直等待,直到一个给定的 Vue 组件属性有特定的值:
// 一直等待,直到组件属性有特定的值
$browser->waitUntilVue('user.name', 'Taylor', '@user');
// 一直等待,直到组件不包含给定的值
$browser->waitUntilVueIsNot('user.name', null, '@user');
等待回调函数
Dusk 中的许多 "wait" 方法都依赖于底层方法 waitUsing
。你可以直接用这个方法去等待一个回调函数返回 true
。waitUsing
方法接收一个最大的等待秒数,闭包执行的内部时间,闭包,以及一个可选的失败信息:
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");
滚动到视图中
有时您可能无法单击某个元素,因为该元素在浏览器的可见区域之外。 scrollIntoView
方法可以将元素滚动到浏览器可视窗口内:
$browser->scrollIntoView('selector')
->click('selector');
创建 Vue 断言
Dusk 甚至还允许你对 Vue 组件数据的状态进行断言。比如:假设你的应用有如下的 Vue 组件:
// HTML...
<profile dusk="profile-component"></profile>
//组件的定义
Vue.component('profile', {
template: '<div>{{ user.name }}</div>',
data: function () {
return {
user: {
name: 'Taylor'
}
};
}
});
你可以像这样对 Vue 组件状态进行断言:
/**
* Vue 基础测试案例
*
* @return void
*/
public function testVue()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
}
可用的断言
Dusk 提供了各种你可以对应用使用的断言。所有可用的断言列表如下:
assertTitle
assertTitleContains
assertUrlIs
assertSchemeIs
assertSchemeIsNot
assertHostIs
assertHostIsNot
assertPortIs
assertPortIsNot
assertPathBeginsWith
assertPathIs
assertPathIsNot
assertRouteIs
assertQueryStringHas
assertQueryStringMissing
assertFragmentIs
assertFragmentBeginsWith
assertFragmentIsNot
assertHasCookie
assertHasPlainCookie
assertCookieMissing
assertPlainCookieMissing
assertCookieValue
assertPlainCookieValue
assertSee
assertDontSee
assertSeeIn
assertDontSeeIn
assertSourceHas
assertSourceMissing
assertSeeLink
assertDontSeeLink
assertInputValue
assertInputValueIsNot
assertChecked
assertNotChecked
assertRadioSelected
assertRadioNotSelected
assertSelected
assertNotSelected
assertSelectHasOptions
assertSelectMissingOption
assertSelectMissingOptions
assertSelectHasOption
assertValue
assertAttribute
assertAriaAttribute
assertDataAttribute
assertVisible
assertPresent
assertMissing
assertDialogOpened
assertEnabled
assertDisabled
assertButtonEnabled
assertButtonDisabled
assertFocused
assertNotFocused
assertAuthenticated
assertGuest
assertAuthenticatedAs
assertVue
assertVueIsNot
assertVueContains
assertVueDoesNotContain
assertTitle
断言页面的标题(title)为给定值:
$browser->assertTitle($title);
assertTitleContains
断言页面标题(title)包含给定值:
$browser->assertTitleContains($title);
assertUrlIs
断言当前的 URL(不包含 query 字符串)满足给定的字符串:
$browser->assertUrlIs($url);
assertSchemeIs
断言当前的 URL scheme 满足给定的 scheme:
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
断言当前的 URL scheme 不满足给定的 scheme:
$browser->assertSchemeIsNot($scheme);
assertHostIs
断言当前的 URL host 满足给定的 host:
$browser->assertHostIs($host);
assertHostIsNot
断言当前的 URL host 不满足给定的 host:
$browser->assertHostIsNot($host);
assertPortIs
断言当前的 URL 端口满足给定的端口:
$browser->assertPortIs($port);
assertPortIsNot
断言当前的 URL 端口不满足给定的端口:
$browser->assertPortIsNot($port);
assertPathBeginsWith
断言当前的 URL 以给定的字符串开头:
$browser->assertPathBeginsWith($path);
assertPathIs
断言当前的路径满足给定的路径:
$browser->assertPathIs('/home');
assertPathIsNot
断言当前的路径不满足给定的路径:
$browser->assertPathIsNot('/home');
assertRouteIs
断言给定的 URL 不满足给定的命名路由的 URL:
$browser->assertRouteIs($name, $parameters);
assertQueryStringHas
断言给定的 query 参数存在:
$browser->assertQueryStringHas($name);
断言给定的 query 参数存在,并且是给定的值:
$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
断言给定的 query 参数不存在:
$browser->assertQueryStringMissing($name);
assertFragmentIs
断言当前的 fragment 满足给定的 fragment:
$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
断言当前的 fragment 以给定的 fragment 开头:
$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
断言当前的 fragment 不满足给定的 fragment:
$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);
assertSourceHas
断言在页面中存在给定的源码:
$browser->assertSourceHas($code);
assertSourceMissing
断言页面中没有给定的源码:
$browser->assertSourceMissing($code);
assertSeeLink
断言在页面中存在指定的链接:
$browser->assertSeeLink($linkText);
assertDontSeeLink
断言页面中没有指定的链接:
$browser->assertDontSeeLink($linkText);
assertInputValue
断言输入框(input)有给定的值:
$browser->assertInputValue($field, $value);
assertInputValueIsNot
断言输入框没有给定的值:
$browser->assertInputValueIsNot($field, $value);
assertChecked
断言复选框(checkbox)被选中:
$browser->assertChecked($field);
assertNotChecked
断言复选框没有被选中:
$browser->assertNotChecked($field);
assertRadioSelected
断言单选框(radio)被选中:
$browser->assertRadioSelected($field, $value);
assertRadioNotSelected
断言单选框没有被选中:
$browser->assertRadioNotSelected($field, $value);
assertSelected
断言下拉框有给定的值:
$browser->assertSelected($field, $value);
assertNotSelected
断言下拉框没有给定的值:
$browser->assertNotSelected($field, $value);
assertSelectHasOptions
断言给定的数组值是可选的:
$browser->assertSelectHasOptions($field, $values);
assertSelectMissingOption
断言给定的值是不可选的:
$browser->assertSelectMissingOption($field, $value);
assertSelectMissingOptions
断言给定的数组值是不可选的:
$browser->assertSelectMissingOptions($field, $values);
assertSelectHasOption
断言给定的值在给定的地方是可供选择的:
$browser->assertSelectHasOption($field, $value);
assertValue
断言选择器范围内的元素存在指定的值:
$browser->assertValue($selector, $value);
assertAttribute
断言与给定选择器匹配的元素在提供的属性中具有给定的值:
$browser->assertAttribute($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);
assertMissing
断言选择器范围内的元素不存在:
$browser->assertMissing($selector);
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
断言 Vue 组件数据的属性匹配指定的值:
$browser->assertVue($property, $value, $componentSelector = null);
assertVueIsNot
断言 Vue 组件数据的属性不匹配指定的值:
$browser->assertVueIsNot($property, $value, $componentSelector = null);
assertVueContains
断言 Vue 组件数据的属性是一个数组,并且该数组包含指定的值:
$browser->assertVueContains($property, $value, $componentSelector = null);
assertVueDoesNotContain
断言 Vue 组件数据的属性是一个数组,并且该数组不包含指定的值:
$browser->assertVueDoesNotContain($property, $value, $componentSelector = null);
Pages
有时候,需要测试一系列复杂的动作,这会使得测试代码难以阅读和理解。通过页面可以定义出语义化的动作,然后在指定页面中可以使用单个方法。页面还可以定义应用或单个页面通用选择器的快捷方式。
生成页面
dusk:page
Artisan 命令可以生成页面对象。所有的页面对象都位于 tests/Browser/Pages
目录:
php artisan dusk:page Login
配置页面
页面默认拥有 3 个方法: url
, assert
和 elements
。 在这里我们先详述 url
和 assert
方法, elements
方法将会 选择器简写中详述。
url
方法
url
方法应该返回表示页面 URL 的路径。 Dusk 将会在浏览器中使用这个 URL 来导航到具体页面:
/**
* 获得页面 URL 路径。
*
* @return string
*/
public function url()
{
return '/login';
}
assert
方法
assert
方法可以作出任何断言来验证浏览器是否在指定页面上。这个方法并不是必须的。你可以根据你自己的需求来做出这些断言。这些断言会在你导航到这个页面的时候自动执行:
/**
* 断言浏览器当前处于指定页面。
*
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertPathIs($this->url());
}
导航至页面
一旦页面配置好之后,你可以使用 visit
方法导航至页面:
use Tests\Browser\Pages\Login;
$browser->visit(new Login);
您可以使用 visitRoute
方法导航到命名路由:
$browser->visitRoute('login');
您可以使用 back
和 forward
方法浏览上一个页面或下一个页面:
$browser->back();
$browser->forward();
您可以使用 refresh
方法刷新页面:
$browser->refresh();
有时您可能已经在给定的页面上,需要将页面的选择器和方法「加载」到当前的测试上下文中。 这在通过按钮重定向到指定页面而没有明确导航到该页面时很常见。 在这种情况下,您可以使用 on
方法加载页面:
use Tests\Browser\Pages\CreatePlaylist;
$browser->visit('/dashboard')
->clickLink('Create Playlist')
->on(new CreatePlaylist)
->assertSee('@create');
选择器简写
elements
方法允许你为页面中的任何 CSS 选择器定义简单易记的简写。例如,让我们为应用登录页中的 email 输入框定义一个简写:
/**
* 获取页面的元素简写
*
* @return array
*/
public function elements()
{
return [
'@email' => 'input[name=email]',
];
}
现在你可以用这个简写来代替之前在页面中使用的完整 CSS 选择器:
$browser->type('@email', 'taylor@laravel.com');
全局的选择器简写
安装 Dusk 之后,Page
基类存放在你的 tests/Browser/Pages
目录。该类中包含一个 siteElements
方法,这个方法可以用来定义全局的选择器简写,这样在你应用中每个页面都可以使用这些全局选择器简写了:
/**
* 获取站点全局的选择器简写
*
* @return array
*/
public static function siteElements()
{
return [
'@element' => '#selector',
];
}
页面方法
除了页面中已经定义的默认方法之外,你还可以定义在整个测试过程中会使用到的其他方法。例如,假设我们正在开发一个音乐管理应用,在应用中都可能需要一个公共的方法来创建列表,而不是在每一页、每一个测试类中都重写一遍创建播放列表的逻辑,这时候你可以在你的页面类中定义一个 createPlaylist
方法:
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class Dashboard extends Page
{
// 其他页面方法...
/**
* 创建一个新的播放列表
*
* @param \Laravel\Dusk\Browser $browser
* @param string $name
* @return void
*/
public function createPlaylist(Browser $browser, $name)
{
$browser->type('name', $name)
->check('share')
->press('Create Playlist');
}
}
方法被定义之后,你可以在任何使用到该页的测试中使用这个方法了。浏览器实例会自动传递该页面方法:
use Tests\Browser\Pages\Dashboard;
$browser->visit(new Dashboard)
->createPlaylist('My Playlist')
->assertSee('My Playlist');
组件
组件类似于 Dusk 的 「页面对象」,不过它更多的是贯穿整个应用程序中频繁重用的 UI 和功能片断,比如说导航条或信息通知弹窗。因此,组件并不会绑定于某个明确的 URL。
生成组件
为了生成一个组件,使用 Artisan 命令 dusk:component
即可生成组件。新生成的组件位于 test/Browser/Components
目录中:
php artisan dusk:component DatePicker
如上所示,这是生成一个 「日期选择器」(date picker) 组件的示例,这个组件可能会贯穿使用在你应用程序的许多页面中。在整个测试套件的大量测试页面中,手动编写日期选择的浏览器自动化逻辑会非常麻烦。 更方便的替代办法是,定义一个表示日期选择器的 Dusk 组件,然后把自动化逻辑封装在该组件内:
<?php
namespace Tests\Browser\Components;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;
class DatePicker extends BaseComponent
{
/**
* 获取组件的 root selector
*
* @return string
*/
public function selector()
{
return '.date-picker';
}
/**
* 浏览器包含组件的断言
*
* @param Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser->assertVisible($this->selector());
}
/**
* 读取组件的元素快捷方式
*
* @return array
*/
public function elements()
{
return [
'@date-field' => 'input.datepicker-input',
'@year-list' => 'div > div.datepicker-years',
'@month-list' => 'div > div.datepicker-months',
'@day-list' => 'div > div.datepicker-days',
];
}
/**
* 选择给定日期
*
* @param \Laravel\Dusk\Browser $browser
* @param int $year
* @param int $month
* @param int $day
* @return void
*/
public function selectDate($browser, $year, $month, $day)
{
$browser->click('@date-field')
->within('@year-list', function ($browser) use ($year) {
$browser->click($year);
})
->within('@month-list', function ($browser) use ($month) {
$browser->click($month);
})
->within('@day-list', function ($browser) use ($day) {
$browser->click($day);
});
}
}
使用组件
组件定义一旦完成,在任何测试页面的日期选择器中选定一个日期就很轻松了。并且,如果需要修改选定日期的逻辑,仅修改该组件即可:
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
/**
* 基本的组件测试示例
*
* @return void
*/
public function testBasicExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function ($browser) {
$browser->selectDate(2019, 1, 30);
})
->assertSee('January');
});
}
}
持续集成
注意:在添加持续集成配置文件之前,请确保你的
.env.testing
文件中APP_URL
配置项的值是http://127.0.0.1:8000
。
CircleCI
如果使用 CircleCI 运行 Dusk 测试,则可以参考此配置文件作为起点。与 TravisCI 一样,我们将使用 php artisan serve
命令启动 PHP 的内置 Web 服务器:
version: 2
jobs:
build:
steps:
- run: sudo apt-get install -y libsqlite3-dev
- run: cp .env.testing .env
- run: composer install -n --ignore-platform-reqs
- run: php artisan key:generate
- run: php artisan dusk:chrome-driver
- run: npm install
- run: npm run production
- run: vendor/bin/phpunit
- run:
name: Start Chrome Driver
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true
- run:
name: Run Laravel Server
command: php artisan serve
background: true
- run:
name: Run Laravel Dusk Tests
command: php artisan dusk
- store_artifacts:
path: tests/Browser/screenshots
- store_artifacts:
path: tests/Browser/console
- store_artifacts:
path: storage/logs
Codeship
想要在 Codeship 中运行 Dusk 测试,添加以下的命令到你的 Codeship 项目,这些命令这是一个入门示例,如果需要的话你可以添加更多额外命令:
phpenv local 7.2
cp .env.testing .env
mkdir -p ./bootstrap/cache
composer install --no-interaction --prefer-dist
php artisan key:generate
php artisan dusk:chrome-driver
nohup bash -c "php artisan serve 2>&1 &" && sleep 5
php artisan dusk
Heroku CI
要想在 Heroku CI 上运行 Dusk 测试,添加以下 Google Chrome buildpack 和 脚本到 Heroku 的 app.json
文件中:
{
"environments": {
"test": {
"buildpacks": [
{ "url": "heroku/php" },
{ "url": "https://github.com/heroku/heroku-buildpack-google-chrome" }
],
"scripts": {
"test-setup": "cp .env.testing .env",
"test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve > /dev/null 2>&1 &' && php artisan dusk"
}
}
}
}
Travis CI
要想通过 Travis CI 运行 Dusk 测试,可以使用下面这个 .travis.yml
配置。由于 Travis CI 不是一个图形化的环境,我们还需要一些额外的步骤以便启动 Chrome 浏览器。此外,我们将会使用 php artisan serve
来启动 PHP 自带的 Web 服务器:
language: php
php:
- 7.3
addons:
chrome: stable
install:
- cp .env.testing .env
- travis_retry composer install --no-interaction --prefer-dist --no-suggest
- php artisan key:generate
- php artisan dusk:chrome-driver
before_script:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
- php artisan serve &
script:
- php artisan dusk
GitHub Actions
如果你正在使用 Github Actions 来运行你的 Dusk 测试,你应该以这个配置文件为起点。像 TravisCI 一样,我们将使用 php artisan serve
命令来启动 php 的 web 服务:
name: CI
on: [push]
jobs:
dusk-php:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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 --no-suggest --prefer-dist --optimize-autoloader
- name: Generate Application Key
run: php artisan key:generate
- name: Upgrade Chrome Driver
run: php artisan dusk:chrome-driver `/opt/google/chrome/chrome --version | cut -d " " -f3 | cut -d "." -f1`
- name: Start Chrome Driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux &
- name: Run Laravel Server
run: php artisan serve &
- name: Run Dusk Tests
env:
APP_URL: "http://127.0.0.1:8000"
run: php artisan dusk
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: