* * * * *
[TOC]
## 介紹
Laravel Dusk 提供了富有表現力、簡單易用的瀏覽器自動化及測試 API 。默認情況下,Dusk 不需要在你的機器上安裝 JDK 或者 Selenium 。而是需要使用單獨的?[Chrome 驅動?](https://sites.google.com/a/chromium.org/chromedriver/home)進行安裝。當然,你也可以自由使用其他的兼容 Selenium 的驅動程序。
## 安裝
你應該先向你的 Composer 依賴添加?`laravel/dusk`?:
~~~
composer require --dev laravel/dusk
~~~
當 Dusk 安裝完成后,你應該注冊?`Laravel\Dusk\DuskServiceProvider`?服務提供者。通常情況下,它將會在 Laravel 服務提供者的自動注冊過程中自動安裝好。
> {note} 如果你是手動注冊 Dusk 服務提供者,**一定不能**?在你的生產環境中注冊,這樣可能會導致一些不守規矩的用戶擁有控制你應用的權限。
安裝好 Dusk 包后,運行?`dusk:install`?Artisan 命令:
~~~
php artisan dusk:install
~~~
`Browser`?目錄將會在?`tests`?目錄下被創建,并且包含一個測試用例。然后,在你的?`.env`?文件中設置?`APP_URL`?變量。這個值應該與你在瀏覽器中打開本應用的 URL 匹配。
要運行測試,使用?`dusk`?Artisan 命令。?`dusk`?命令與?`phpunit`?命令接收同樣的參數:
~~~
php artisan dusk
~~~
### 使用其他瀏覽器
默認情況下, Dusk 使用 Google Chrome 瀏覽器和一個單獨的?[ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/home)?的安裝運行你的瀏覽器測試。當然,你可以運行你自己的 Selenium 服務,用任何你想用的瀏覽器進行測試。
如果要這么做,打開?`tests/DuskTestCase.php`?文件,這個是應用測試用例的基類。在這個文件中,你可以移除對?`startChromeDriver`?方法的調用。這樣 Dusk 就不會自動啟動 ChromeDriver 了。
~~~
/**
* 準備執行 Dusk 測試。
*
* @beforeClass
* @return void
*/
public static function prepare()
{
// static::startChromeDriver();
}
~~~
然后,你可以修改?`driver`?方法來連接到你選定的 URL 和端口。此外,你可以修改「期望能力」這個值,它將會被傳遞到 Web 驅動:
~~~
/**
* 創建 RemoteWebDriver 實力。
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}
~~~
## 開始
### 生成測試
要生成一個 Dusk 測試,使用?`dusk:make`?Artisan 命令。生成的測試將會被放在?`tests/Browser`?目錄:
~~~
php artisan dusk:make LoginTest
~~~
### 運行測試
使用?`dusk`?Artisan 命令來運行你的瀏覽器測試:
~~~
php artisan dusk
~~~
`dusk`?命令接受任何能讓 PHPUnit 正常運行所接受的參數。例如,讓你可以在指定[?group?](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.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();
}
~~~
另外,如果你是在非 9515 端口運行 ChromeDriver,你需要在`tests/DuskTestCase.php`?文件中修改?`driver`?方法:
~~~
/**
* 創建 RemoteWebDriver 實例。
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
~~~
### 環境處理
在你的項目根目錄創建?`.env.dusk.{environment}`?文件來強制 Dusk 使用自己的環境文件來運行測試。簡單的說,如果你想用?`local`?環境來運行?`dusk`?命令,你需要創建一個?`.env.dusk.local`?文件。
運行測試的時候,Dusk 會備份你的?`.env`?文件并且重命名你的 Dusk 環境文件為?`.env`。當測試結束后,它會恢復你的?`.env`?文件。
### 創建瀏覽器
讓我們先來寫一個測試用例,這個例子可以驗證我們是否能夠登錄系統。生成測試例子之后,我們可以修改它并讓它可以跳轉到登錄界面,輸入某些登錄信息之后,點擊「登錄」按鈕。通過?`browse`?方法來創建一個瀏覽器實例:
~~~
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;
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', 'secret')
->press('Login')
->assertPathIs('/home');
});
}
}
~~~
在上面的例子中,`browse`?方法接收了一個回調參數。Dusk 會自動將這個瀏覽器實例注入到回調過程中,而且這個瀏覽器實例可以讓你和你的應用之間進行交互和斷言。
> {tip} 這個測試例子可以用?`make:auth`?Artisan 命令來生成登錄界面。
#### 創建多個瀏覽器
有時候你可能需要多個瀏覽器才能正確的進行測試。例如,使用多個瀏覽器測試通過 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();
~~~
### 認證
你可能經常會測試一些需要認證的頁面。你可以使用 Dusk 的?`loginAs`?方法來避免每個測試都去登陸頁面登陸一次。?`loginAs`?可以使用用戶 ID 或者用戶模型實例:
~~~
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
~~~
> {note} 使用?`loginAs`?方法后,該用戶的 session 將會持久化的供其他測試用例使用。
### 數據庫遷移
就像上面的認證例子一樣,當你的測試用例需要遷移的時候,你不應該使用?`RefreshDatabase`?的 trait。?`RefreshDatabase`?的 trait 使用了不適用于 HTTP 請求的數據庫事務。所以,我們要用?`DatabaseMigrations`?中的 trait:
~~~
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Chrome;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
}
~~~
## 與元素交互
### Dusk 選擇器
選擇一個好的 CSS 選擇器用于元素交互是編寫 Dush 測試最困難的部分之一。隨著時間推移,前端的更改可能會導致類似以下的 CSS 選擇器中斷測試:
~~~
// HTML...
<button>Login</button>
// 測試...
$browser->click('.login-page .container div > button');
~~~
Dusk 選擇器讓你專注于編寫有效的測試,而不是去記憶 CSS 選擇器。要定義一個選擇器,只需在你的 HTML 元素中添加一個?`dusk`?屬性。然后,在選擇器前面添加?`@`?去操作 Dusk 測試中的附加元素:
~~~
// HTML...
<button dusk="login-button">Login</button>
// 測試...
$browser->click('@login-button');
~~~
### 點擊鏈接
要點擊鏈接的話,你可以在瀏覽器實例上使用?`clickLink`?方法。`clickLink`?方法將會點擊指定顯示文本的鏈接:
~~~
$browser->clickLink($linkText);
~~~
> {note} 這個方法可以與 jQuery 進行交互。如果頁面上沒有 jQuery,Dusk 會自動將其注入頁面,保證在測試的期間可用:
### 文本、值和屬性
#### 檢索和設置值
Dusk 提供了幾種與當前顯示文本,值和屬性進行交互的方法。例如,要獲取與指定選擇器匹配的元素的「值」,請使用?`value`?方法:
~~~
// 檢索值...
$value = $browser->value('selector');
// 設置值...
$browser->value('selector', 'value');
~~~
#### 檢索文本
`text`?方法可用來匹配指定選擇器中元素的顯示文本:
~~~
$text = $browser->text('selector');
~~~
#### 檢索屬性
最后,`attribute`?方法可用來匹配指定選擇器中元素的屬性:
~~~
$attribute = $browser->attribute('selector', 'value');
~~~
### 使用表單
#### 輸入值
Dusk 提供了與表單和 input 元素交互的各種方法。首先,讓我們來看一個在 input 框中輸入文本的示例:
~~~
$browser->type('email', 'taylor@laravel.com');
~~~
注意,雖然 type 方法可以傳遞 CSS 選擇器作為一個參數,但這并不是強制要求的。如果傳入的不是 CSS 選擇器,Dusk 會嘗試搜索與 name 屬性相同的 input 框。如果還沒有找到,Dusk 會嘗試查找傳入值與 name 屬性相同的?`textarea`。
要想將文本附加到一個字段之后而且不清除其內容,你可以使用`append`?方法:
~~~
$browser->type('tags', 'foo')
->append('tags', ', bar, baz');
~~~
你可以使用?`clear`?方法來清除輸入值:
~~~
$browser->clear('email');
~~~
#### 下拉菜單
你可以使用?`select`?方法來選擇下拉菜單中的某個選項。類似于?`type`?方法,`select`?方法并不是一定要傳入 CSS 選擇器。當使用?`select`?方法的時候,應該傳遞選項實際的值而不是它的顯示文本:
~~~
$browser->select('size', 'Large');
~~~
你也可以通過省略第二個參數來隨機選擇一個選項:
~~~
$browser->select('size');
~~~
#### 復選框
你可以使用?`check`?方法來選中一個復選框。像其他許多與 input 相關的方法,并不是必須傳入 CSS 選擇器。如果準確的選擇器找不到,Dusk 會搜索與?`name`?屬性匹配的復選框:
~~~
$browser->check('terms');
$browser->uncheck('terms');
~~~
#### 單選按鈕
你可以使用?`radio`?方法來選擇某個單選選項。像很多其他的與輸入相關的方法一樣,它也并不是必須傳入 CSS 選擇器。如果準確的選擇器無法找到的時候,Dusk 會搜索能夠與?`name`?屬性或者?`value`?屬性匹配的單選按鈕:
~~~
$browser->radio('version', 'php7');
~~~
### 附加文件
`attach`?方法可以附加一個文件到?`file`?input 框中。像很多其他的與輸入相關的方法一樣,它也并不是必須傳入 CSS 選擇器。如果準確的選擇器沒法找到的時候,Dusk 會搜索與?`name`?屬性匹配的文件輸入框:
~~~
$browser->attach('photo', __DIR__.'/photos/me.png');
~~~
> {note} attach 方法要求在你的服務器上安裝并啟用PHP的?`Zip`?擴展。
### 使用鍵盤
`keys`?方法讓你可以在指定元素中輸入比?`type`?方法更加復雜的輸入序列。例如,你可以在輸入值的同時按下按鍵。在這個例子中,輸入?`taylor`?時,`shift`?鍵也同時被按下。當?`taylor`?輸入完之后,將會輸入`otwell`?而不會按下任何按鍵:
~~~
$browser->keys('selector', ['{shift}', 'taylor'], 'otwell');
~~~
你甚至可以在你的應用中選中某個元素之后按下「快捷鍵」:
~~~
$browser->keys('.app', ['{command}', 'j']);
~~~
> {tip} 所有包在?`{}`?中的鍵盤按鍵,都應該與?`Facebook\WebDriver\WebDriverKeys`?類中定義的常量一致。可以在?[GitHub?](https://github.com/facebook/php-webdriver/blob/community/lib/WebDriverKeys.php)中找到這些類。
### 使用鼠標
#### 點擊元素
`click`?用來「點擊」與指定選擇器匹配的元素:
~~~
$browser->click('.selector');
~~~
#### Mouseover
`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);
~~~
### 元素作用域
有時候你可能希望某個與選擇器匹配的元素中執行一系列的操作。 例如,你可能希望在某個表格中斷言有某些文本,然后在那個表格中點擊按鈕去操作這些文本。你可以使用?`with`?方法來達到這個目的。`with`?方法的回調參數中,所有的操作都作用在同一個原始選擇器上:
~~~
$browser->with('.table', function ($table) {
$table->assertSee('Hello World')
->clickLink('Delete');
});
~~~
### 等待元素
由于在測試應用的時候經常會用到 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);
~~~
#### 可用元素的作用域
有時候,你可能想要等待與指定選擇器匹配的元素,然后與這元素進行交互。例如,你可能需要等待某個模態窗口可用,然后在模態窗口中點擊「OK」按鈕。在這種情況下,可以使用?`whenAvailable`?方法。所有閉包中的操作都針對這個原始的元素:
~~~
$browser->whenAvailable('.modal', function ($modal) {
$modal->assertSee('Hello World')
->press('OK');
});
~~~
#### 等待文本
`waitForText`?方法用于等待指定文本,直到顯示在頁面中為止:
~~~
// 最多為這個文本的顯示等待 5 秒...
$browser->waitForText('Hello World');
// 最多為這個文本的顯示等待 1 秒...
$browser->waitForText('Hello World', 1);
~~~
#### 等待超鏈接
`waitForLink`?方法用來等待指定鏈接文本,直到鏈接文本顯示在頁面中為止:
~~~
// 最多為這個鏈接的顯示等待 5 秒...
$browser->waitForLink('Create');
// 最多為這個鏈接的顯示等待 1 秒...
$browser->waitForLink('Create', 1);
~~~
#### 等待頁面跳轉
在進行例如?`$browser->assertPathIs('/home')`的路徑斷言時,如果?`window.location.pathname`?為異步完成,斷言就會失敗。你需要使用?`waitForLocation`?方法去等待指定的跳轉:
~~~
$browser->waitForLocation('/secret');
~~~
#### 等待頁面重載
如果你需要在頁面重載后進行斷言,你可以使用?`waitForReload`?方法:
~~~
$browser->click('.some-action')
->waitForReload()
->assertSee('something');
~~~
#### 等待 JavaScript 表達式
有時候你可能想要暫停測試用例的執行,直到指定的 JavaScript 表達式計算結果為?`true`。使用?`waitUntil`?方法可以讓你很容易做到這一點。傳遞表達式給方法的時候,你不需要包括?`return`?關鍵詞或者結束分號:
~~~
// 最多為表達式的成立等待 5 秒...
$browser->waitUntil('App.dataLoaded');
$browser->waitUntil('App.data.servers.length > 0');
// 最多為表達式的成立等待 1 秒...
$browser->waitUntil('App.data.servers.length > 0', 1);
~~~
#### 等待回調
Dusk 中的許多「等待」方法依賴于?`waitUsing`?方法。該方法可以等待一個回調返回?`true`。`waitUsing`?接受的參數為最大等待秒數、閉包的執行間隔、閉包以及一個可選的錯誤信息。
~~~
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");
~~~
### 作出 Vue 斷言
Dusk 還允許你對?[Vue](https://vuejs.org/)?組件數據的狀態作出斷言。例如,假設您的應用程序包含以下 Vue 組件:
~~~
// HTML...
<profile dusk="profile-component"></profile>
// 定義組件...
Vue.component('profile', {
template: '<div>{{ user.name }}</div>',
data: function () {
return {
user: {
name: 'Taylor'
}
};
}
});
~~~
你可以在 Vue 組件的狀態上作出如下斷言:
~~~
/**
* A basic Vue test example.
*
* @return void
*/
public function testVue()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
}
~~~
## 可用的斷言
Dusk 提供了一系列可用的斷言方法。所有斷言如下:
[assertTitle](#assertTitle_633)
[assertTitleContains](#assertTitleContains_641)
[assertUrlIs](#assertUrlIs_649)
[assertPathBeginsWith](#assertPathBeginsWith_657)
[assertPathIs](#assertPathIs_665)
[assertPathIsNot](#assertPathIsNot_673)
[assertRouteIs](#assertRouteIs_681)
[assertQueryStringHas](#assertQueryStringHas_689)
[assertQueryStringHas](#assertQueryStringHas_697)
[assertQueryStringMissing](#assertQueryStringMissing_705)
[assertHasCookie](#assertHasCookie_713)
[assertCookieMissing](#assertCookieMissing_721)
[assertCookieValue](#assertCookieValue_729)
[assertPlainCookieValue](#assertPlainCookieValue_737)
[assertSee](#assertSee_745)
[assertDontSee](#assertDontSee_753)
[assertSeeIn](#assertSeeIn_761)
[assertDontSeeIn](#assertDontSeeIn_769)
[assertSourceHas](#assertSourceHas_777)
[assertSourceMissing](#assertSourceMissing_785)
[assertSeeLink](#assertSeeLink_793)
[assertDontSeeLink](#assertDontSeeLink_801)
[assertInputValue](#assertInputValue_809)
[assertInputValueIsNot](#assertInputValueIsNot_817)
[assertChecked](#assertChecked_825)
[assertNotChecked](#assertNotChecked_833)
[assertRadioSelected](#assertRadioSelected_841)
[assertRadioNotSelected](#assertRadioNotSelected_849)
[assertSelected](#assertSelected_857)
[assertNotSelected](#assertNotSelected_865)
[assertSelectHasOption](#assertSelectHasOption_873)
[assertSelectMissingOptions](#assertSelectMissingOptions_881)
[assertSelectHasOption](#assertSelectHasOption_889)
[assertValue](#assertValue_897)
[assertVisible](#assertVisible_905)
[assertMissing](#assertMissing_913)
[assertDialogOpened](#assertDialogOpened_921)
[assertVue](#assertVue_929)
[assertVueIsNot](#assertVueIsNot_937)
#### assertTitle
斷言網頁標題匹配指定的文本:
~~~
$browser->assertTitle($title);
~~~
#### assertTitleContains
斷言網頁標題含有指定的文本:
~~~
$browser->assertTitleContains($title);
~~~
#### assertUrlIs
斷言當前 URL (不帶查詢字符串) 匹配指定的字符串:
~~~
$browser->assertUrlIs($url);
~~~
#### assertPathBeginsWith
斷言當前 URL 開始于指定的路徑:
~~~
$browser->assertPathBeginsWith($path);
~~~
#### assertPathIs
斷言當前路徑匹配指定的路徑:
~~~
$browser->assertPathIs('/home');
~~~
#### assertPathIsNot
斷言當前路徑不匹配指定的路徑:
~~~
$browser->assertPathIsNot('/home');
~~~
#### assertRouteIs
斷言當前 URL 匹配指定的命名路由的 URL:
~~~
$browser->assertRouteIs($name, $parameters);
~~~
#### assertQueryStringHas
斷言存在指定的查詢字符串參數:
~~~
$browser->assertQueryStringHas($name);
~~~
#### assertQueryStringHas
斷言存在指定的查詢字符串參數以及值:
~~~
$browser->assertQueryStringHas($name, $value);
~~~
#### assertQueryStringMissing
斷言不存在指定的查詢字符串參數:
~~~
$browser->assertQueryStringMissing($name);
~~~
#### assertHasCookie
斷言存在指定的 cookie:
~~~
$browser->assertHasCookie($name);
~~~
#### assertCookieMissing
斷言不存在指定的 cookie:
~~~
$browser->assertCookieMissing($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
斷言輸入框存在指定的值:
~~~
$browser->assertInputValue($field, $value);
~~~
#### assertInputValueIsNot
斷言輸入框不存在指定的值:
~~~
$browser->assertInputValueIsNot($field, $value);
~~~
#### assertChecked
斷言指定的復選框被選中:
~~~
$browser->assertChecked($field);
~~~
#### assertNotChecked
斷言指定的復選框未選中:
~~~
$browser->assertNotChecked($field);
~~~
#### assertRadioSelected
斷言指定的單選按鈕被選取:
~~~
$browser->assertRadioSelected($field, $value);
~~~
#### assertRadioNotSelected
斷言指定的單選按鈕未選取:
~~~
$browser->assertRadioNotSelected($field, $value);
~~~
#### assertSelected
斷言下拉框被選取指定的值:
~~~
$browser->assertSelected($field, $value);
~~~
#### assertNotSelected
斷言下拉框未選取指定的值:
~~~
$browser->assertNotSelected($field, $value);
~~~
#### assertSelectHasOption
斷言可選到指定數組中的值:
~~~
$browser->assertSelectHasOptions($field, $values);
~~~
#### assertSelectMissingOptions
斷言選取不到指定數組中的值:
~~~
$browser->assertSelectMissingOptions($field, $values);
~~~
#### assertSelectHasOption
斷言可選到指定的值:
~~~
$browser->assertSelectHasOption($field, $value);
~~~
#### assertValue
斷言選擇器范圍內的元素存在指定的值:
~~~
$browser->assertValue($selector, $value);
~~~
#### assertVisible
斷言選擇器范圍內的元素可見:
~~~
$browser->assertVisible($selector);
~~~
#### assertMissing
斷言選擇器范圍內的元素隱藏:
~~~
$browser->assertMissing($selector);
~~~
#### assertDialogOpened
斷言 JavaScript 對話框以指定的信息打開:
~~~
$browser->assertDialogOpened($message);
~~~
#### assertVue
斷言 Vue 組件數據的屬性匹配指定的值:
~~~
$browser->assertVue($property, $value, $componentSelector = null);
~~~
#### assertVueIsNot
斷言 Vue 組件數據的屬性不匹配指定的值:
~~~
$browser->assertVueIsNot($property, $value, $componentSelector = null);
~~~
## Page
有時候,需要測試一系列復雜的動作,這會使得測試代碼難以閱讀和理解。通過頁面可以定義出語義化的動作,然后在指定頁面中可以使用單個方法。頁面還可以定義應用或單個頁面通用選擇器的快捷方式。
### 生成頁面
`dusk:page`?Artisan 命令可以生成頁面。所有的頁面對象都位于?`tests/Browser/Pages`?目錄:
~~~
php artisan dusk:page Login
~~~
### 配置頁面
頁面默認擁有 3 個方法:?`url`,?`assert`?和?`elements`。 在這里我們先詳述?`url`?和?`assert`?方法。`elements`?方法將會?[在下面詳細描述](https://laravel-china.org/docs/laravel/5.6/dusk/1411#shorthand-selectors)。
#### `url`?方法
`url`?方法應該返回表示頁面 URL 的路徑。 Dusk 將會在瀏覽器中使用這個 URL 來導航到具體頁面:
~~~
/**
* 獲得頁面 URL 路徑
*
* @return string
*/
public function url()
{
return '/login';
}
~~~
#### The?`assert`?Method
`assert`?方法可以作出任何斷言來驗證瀏覽器是否在指定頁面上。這個方法并不是必須的。你可以根據你自己的需求來做出這些斷言。這些斷言會在你瀏覽到這個頁面的時候自動執行:
~~~
/**
* 斷言瀏覽器是否正在指定頁面。
*
* @return void
*/
public function assert(Browser $browser)
{
$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 選擇器定義簡單易記的簡寫。例如,讓我們為應用登錄頁中的?`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',
'@month-list' => 'div > div.datepicker-months',
'@day-list' => 'div > div.datepicker-days',
];
}
/**
* 選擇給定日期
*
* @param \Laravel\Dusk\Browser $browser
* @param int $month
* @param int $year
* @return void
*/
public function selectDate($browser, $month, $year)
{
$browser->click('@date-field')
->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 Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class ExampleTest extends DuskTestCase
{
/**
* 基本的組件測試示例。
*
* @return void
*/
public function testBasicExample()
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function ($browser) {
$browser->selectDate(1, 2018);
})
->assertSee('January');
});
}
}
~~~
## 持續集成
### CircleCI
#### CircleCI 1.0
如果你要使用 CircleCI 1.0 運行 Dusk 測試,可能需要使用以下配置進行啟動。和 TravisCI 一樣,我們需要使用?`php artisan serve`?命令去運行 PHP 內置服務器:
~~~
dependencies:
pre:
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome.deb
- sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome
- rm google-chrome.deb
test:
pre:
- "./vendor/laravel/dusk/bin/chromedriver-linux":
background: true
- cp .env.testing .env
- "php artisan serve":
background: true
override:
- php artisan dusk
~~~
#### CircleCI 2.0
如果你使用 CircleCI 2.0 運行你的 Dusk 測試,你需要將以下 steps 添加到你的 build 中:
~~~
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: 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
~~~
### Codeship
在?[Codeship](https://codeship.com/)?中運行 Dusk 測試,需要在你的 Codeship 項目中添加以下命令。當然,這些命令只是作為范例,你可以根據需要隨意添加額外的命令:
~~~
phpenv local 7.1
cp .env.testing .env
composer install --no-interaction
nohup bash -c "./vendor/laravel/dusk/bin/chromedriver-linux 2>&1 &"
nohup bash -c "php artisan serve 2>&1 &" && sleep 5
php artisan dusk
~~~
### Heroku CI
在?[Heroku CI](https://www.heroku.com/continuous-integration)?中運行 Dusk 測試時,請將下列 Google Chrome 構建包和腳本添加到你的 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 測試時, 我們需要「sudo-enabled」的 Ubuntu 14.04 (Trusty) 環境。由于 Travis CI 不是圖形化環境,我們需要一些額外的步驟去啟動 Chrome 瀏覽器。另外,我們需要使用?`php artisan serve`?命令去啟動 PHP 內置服務器:
~~~
sudo: required
dist: trusty
addons:
chrome: stable
install:
- cp .env.testing .env
- travis_retry composer install --no-interaction --prefer-dist --no-suggest
before_script:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
- php artisan serve &
script:
- php artisan dusk
~~~
- 前言
- 翻譯說明
- 發行說明
- 升級指南
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- Homestead
- Valet
- 部署
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- Facades
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- URL
- Session
- 表單驗證
- 錯誤
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全相關
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試相關
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Scout 全文搜索
- Socialite 社會化登錄