Laravel Dusk 控制台是一款 Laravel 扩展包,能够为你的 Dusk 测试套件提供漂亮的可视面板。通过它,你可以可视化运行 Dusk 测试时涉及的各个步骤,以及查看每个步骤的 DOM 快照。这对于调试浏览器测试、并搞清楚后台做了什么十分有用。同时,你还可以使用浏览器的调试工具来检查 DOM 快照。
<img src="https://user-gold-cdn.xitu.io/2019/1/23/16878695eedc6b8f?w=1240&h=728&f=png&s=505665" class="rm-style">
除了可视面板,此扩展包还提供了 Laravel Dusk 测试监视器。在你对 Dusk 测试进行修改后,便会自动执行测试过程。
该扩展包受到 Javascript 前端测试框架 —— Cypress 的强烈启发。
查看本扩展包,请移步 GitHub 。
什么是 Laravel Dusk?
Laravel Dusk 提供了富有表现力的、易于使用的浏览器自动化和测试 API。使用 Laravel Dusk编写测试用例,像在真正的浏览器上一样。比如,当你想在网站上测试拖放功能时,想要测试Vue组件或其他与 Javascript 相关功能,那么你无法使用 Laravels HTTP 测试 API 本身进行测试。
我认为 Laravel Dusk 是一个非常棒的软件包并且可以简化浏览器测试。
以下是一个用户注册的示例测试,以便你可以了解 Laravel Dusk 的功能:
public function test_can_register()
{
$faker = Factory::create();
$this->browse(function($browser) use ($faker) {
$password = $faker->password(9);
$browser->visit('/register')
->assertSee('Register')
->type('name', $faker->name)
->type('email', $faker->safeEmail)
->type('password', $password)
->type('password_confirmation', $password)
->press('Register')
->assertPathIs('/home');
});
}
要了解更多关于 Laravel Dusk 以及如何开始使用自己的浏览器测试的更多信息,请查看 官方文档。
使用 Laravel Dusk 控制台
在介绍 Laravel Dusk 控制台内部如何运行之前,让我们先瞄一眼如何在 Laravel 应用内安装并使用这个扩展包。
如下步骤假定你已经按照 官方文档 成功地安装了 Laravel Dusk;或者甚至你已经写好了一些 Dusk 测试。
首先,使用 Composer 安装本扩展包。
composer require --dev beyondcode/dusk-dashboard
接下来,打开 Laravel Dusk 生成的 DuskTestCase.php
。你可以在 tests
目录里找到这个文件。
请务必使用本扩展包的测试用例(Test case
)作为基类,而不是 Laravel Dusk 的测试用例。稍后我再告诉你内部原理。
找到此行:
use Laravel\Dusk\TestCase as BaseTestCase;
使用如下内容替换:
use BeyondCode\DuskDashboard\Testing\TestCase as BaseTestCase;
搞定。
现在你可以使用如下命令启动 Laravel Dusk 控制台,并执行你的测试了。
php artisan dusk:dashboard
类似这样的界面便会展示在你的面前:
<img src="https://user-gold-cdn.xitu.io/2019/1/23/168786968f28545f?w=1240&h=728&f=png&s=287914" class="rm-style">
开始测试
只需按下「Start Tests」按钮,即可运行 Laravel Dusk 测试,并观察到你的应用被测试时的输出,以及所发生的行为。
随后,你便会看到 Dusk 测试产生的各种事件出现在你的控制台上。
还有一种启动 Dusk 测试的方法是,只要编辑任意一个测试文件然后保存即可。Laravel Dusk 控制台内置了文件监视器。
调试测试步骤
你可以通过点击展示在列表中的测试行为,来调试和检查它们。点击后,你将会看到 DOM 快照,表示当此行为被记录时的 HTML 页面状态。若此行为以某种方式操作过 DOM,那么你也可以点击 「Before」和「After」按钮在事件发生「之前」或「之后」的 DOM 快照之间进行切换。
如下,一个按下「Register」按钮的小例子:
检查XHR请求
有时候,查看运行测试时发生的有关 XHR 请求的其他信息可能会很有用。例如:你网站上又一个按钮,它将对某个服务端执行 GET 请求。
Dusk Dashboard 允许您记录 XHR 事件,并显示响应状态和响应路径。
默认情况下 XHR 请求检查不会启用,因为它需要你修改浏览器功能。
要启用 XHR 的请求记录,打开你的 DuskTestCase.php
,在文件里,有个 driver
方法,用于设置不同测试操作的 WebDriver。由于此程序包需要对此驱动程序的功能进行一些调整,因此需要使用 $this->enableNetworkLogging
方法调用来封装 DesiredCapabilities
对象。
protected function driver()
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(
'http://localhost:9515', $this->enableNetworkLogging(
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
)
);
}
通过添加此功能,该程序包将启用记录 XHR 请求和响应信息所需的功能。
工作原理
基本思路十分简单:运行一个 WebSocket 服务,控制台用户连接到这个 WebSocket 服务,接着 PHPUnit 便会将浏览器事件和失败信息发送至所有 WebSocket 连接。
以下是具体的实现方式:
在内部,此扩展包向你的 Laravel 应用内添加了一个名为 StartDashboardCommand
的命令。当此命令被执行时,就会 启动 一个由 Ratchet 开发的 WebSocket 服务。最初我考虑基于我同 Freek 一起开发的 Laravel Websockets 实现此功能,然而随后就毙了这个想法。原因很简单,此扩展包仅能用作开发依赖项,并且我不需要 Pusher 或 Laravel 广播功能,因为广播是通过 PHPUnit 内部实现的。
译者注:Freek 意指 Freek Van der Herten。
另,截至目前,此扩展包也已经发布 v1.0.x 稳定版本。
接下来,我添加两条路由到 WebSocket 服务。
$dashboardRoute = new Route('/dashboard', ['_controller' => new DashboardController()], [], [], null, [], ['GET']);
$this->app->routes->add('dashboard', $dashboardRoute);
$eventRoute = new Route('/events', ['_controller' => new EventController()], [], [], null, [], ['POST']);
$this->app->routes->add('events', $eventRoute);
$dashboardRoute
是一条普通 HTTP 控制器路由,用于输出 Laravel Dusk 控制台的 HTML 视图。
就是这么简单,它只做一件事——返回 HTML 视图:
class DashboardController extends Controller
{
public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
{
$connection->send(
str(new Response(
200,
['Content-Type' => 'text/html'],
file_get_contents(__DIR__.'/../../../resources/views/index.html')
))
);
$connection->close();
}
}
$eventRoute
同样是一个 HTTP 路由,但只允许 POST
请求。它被用来在 PHPUnit 和 WebSocket 客户端之间通讯。
同样十分简单,也只做一件事——接收 POST 数据,并广播给所有已连接的 WebSocket 客户端:
class EventController extends Controller
{
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
{
try {
/*
* 如下即为从 PHPUnit 测试发来的 POST 数据,
* 发送到已连接的客户端。
*/
foreach (Socket::$connections as $connection) {
$connection->send($request->getBody());
}
$conn->send(str(new Response(200)));
} catch (Exception $e) {
$conn->send(str(new Response(500, [], $e->getMessage())));
}
$conn->close();
}
}
收集浏览器行为
这是整个扩展包最乏味的部分。因为若想收集所有 Laravel Dusk 方法,并将它们广播到 WebSocket 连接,那么必须代理所有的消息再收集它们。
在本扩展包自定义的 TestCase
类里,我们能够重写(override
)浏览器实例被创建的过程。那么,此处就是我注入自定义的浏览器(Browser
)类的地方。它负责代理现有方法并收集所有行为,同时转发给 WebSocket 连接。
protected function newBrowser($driver)
{
return new Browser($driver);
}
没什么高端操作。接下来,我原本想直接创建一个新类,传给它 Laravel Dusk 的浏览器类,随后使用 __call
魔术方法代理所有的方法。这能够省下一大堆代码,但也会引出两个问题:
用户无法使用 IDE 自动完成、方法提示功能。
对我来说有点忍不了,我认为这是个非常重要的特性 —— 尤其是对于测试工具来说。开发者并不了解 API 的输入和输出,因此需要 IDE 的提示。
另一个问题是,我不仅仅想在浏览器行为发生后记录 DOM 快照,在某些特定的行为发生前,同样想记录快照。
所以这就是我为何不得不像下面这样,代理所有 Laravel Dusk 方法:
/** @inheritdoc */
public function assertTitle($title)
{
$this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);
return parent::assertTitle($title);
}
好了,这样我便能收集并记录各个行为,且依然维持着 IDE 自动完成功能。棒棒哒!
现在你能看到这里的 actionCollector
是 PHPUnit 和 WebSocket 客户端之间的桥梁。它收集获得的信息,并用例如测试名称和 WebSocket POST 推送的端点数据来丰富它:
protected function pushAction(string $name, array $payload)
{
try {
$this->client->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [
RequestOptions::JSON => [
'channel' => 'dusk-dashboard',
'name' => $name,
'data' => $payload,
],
]);
} catch (\Exception $e) {
// Dusk-Dashboard 服务器可能是关闭的。不必惊慌。
}
}
它由 try-catch 包裹来保证即使在 Dusk Dashboard 服务器关闭时 Laravel Dusk 也能正常运行。
UI 界面
最后,值得注意的是,此扩展包在它的面板界面里也有很多说道。它由 TailwindCSS 和 Vue 驱动来展示到来的事件以及过滤它们等等。你可以在这 这 查看起始页面的代码。
差不多就这些了。
更多翻译文章请见 PHP / Laravel 开发者社区 https://laravel-china.org/topics/21190