瀏覽器測試 Dusk
瀏覽器測試 Dusk
Browser Tests (Laravel Dusk)
介紹
Laravel Dusk 提供了富有表現(xiàn)力、簡單易用的瀏覽器自動化及測試 API 。默認情況下,Dusk 不需要在你的機器上安裝 JDK 或者 Selenium 。而是需要使用單獨的 ChromeDriver 進行安裝。當然,你也可以自由使用其他的兼容 Selenium 的驅(qū)動程序。
安裝
你應(yīng)該先向你的 Composer 添加 laravel/dusk
依賴 :
composer require --dev laravel/dusk
{note} 如果你是手動注冊 Dusk 服務(wù)提供者,一定 不能 在你的生產(chǎn)環(huán)境中注冊,這樣可能會導(dǎo)致一些不守規(guī)矩的用戶擁有控制你應(yīng)用的權(quán)限。
安裝好 Dusk 包后,運行 dusk:install
命令:
php artisan dusk:install
Browser
目錄將會在 tests
目錄下被創(chuàng)建,并且包含一個測試用例。接下來,在你的 .env
文件中設(shè)置 APP_URL
變量。這個值應(yīng)該與你在瀏覽器中打開本應(yīng)用的 URL 匹配。
要運行測試,使用 dusk
命令。 dusk
命令可以使用與 phpunit
命令同樣的參數(shù):
php artisan dusk
如果上次運行 dusk
命令時測試失敗,則可以通過使用 dusk:fails
命令重新運行失敗的測試來節(jié)省時間:
php artisan dusk:fails
{注意} Dusk 要求 ChromeDriver 二進制文件是可執(zhí)行的。如果在 Dusk 運行時遇到問題,可以使用以下命令確保二進制文件是可執(zhí)行的:
chmod -R 0755 vendor/laravel/dusk/bin
。
使用其他瀏覽器
默認情況下, Dusk 使用 Google Chrome 瀏覽器和一個單獨的 ChromeDriver 的安裝來運行你的瀏覽器測試。當然,你可以運行你自己的 Selenium 服務(wù),用任何你想用的瀏覽器來進行測試。
如果要這么做,打開 tests/DuskTestCase.php
文件,這個是應(yīng)用測試用例的基類。在這個文件中,你可以移除對 startChromeDriver
方法的調(diào)用。這樣 Dusk 就不會自動啟動 ChromeDriver 了。
/** * 準備執(zhí)行 Dusk 測試。 * * @beforeClass * @return void */ public static function prepare(){ // static::startChromeDriver(); }
然后,你可以修改 driver
方法來連接到你選定的 URL 和端口。此外,你可以修改 「desired capabilities」(期望能力),它將會被傳遞給 WebDriver:
/** * 創(chuàng)建 RemoteWebDriver 實例。 * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ protected function driver(){ return RemoteWebDriver::create( 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() ); }
開始使用
創(chuàng)建測試
要創(chuàng)建一個 Dusk 測試,使用 dusk:make
命令。創(chuàng)建的測試將會被放在 tests/Browser
目錄:
php artisan dusk:make LoginTest
運行測試
使用 dusk
命令來運行你的瀏覽器測試:
php artisan dusk
如果上次運行 dusk
命令時測試失敗,則可以通過使用 dusk:fails
命令重新運行失敗的測試來節(jié)省時間:
php artisan dusk:fails
dusk
命令接受任何能讓 PHPUnit 正常運行的參數(shù)。例如,讓你可以在指定 group 中運行測試:
php artisan dusk --group=foo
手動運行 ChromeDriver
默認情況下,Dusk 會嘗試自動運行 ChromeDriver。如果你在特定的系統(tǒng)中不能運行,可以在運行 dusk
命令前通過手動的方式來運行 ChromeDriver。 如果你選擇手動運行 ChromeDriver,你需要在你的 tests/DuskTestCase.php
文件中注釋掉下面這一行:
/** * 為 Dusk 測試做準備。 * * @beforeClass * @return void */ public static function prepare(){ // static::startChromeDriver(); }
另外,如果你的 ChromeDriver 運行在非 9515 端口 ,你需要修改同一個類中的 driver
方法:
/** * 創(chuàng)建 RemoteWebDriver 實例。 * * @return \Facebook\WebDriver\Remote\RemoteWebDriver */ protected function driver(){ return RemoteWebDriver::create( 'http://localhost:9515', DesiredCapabilities::chrome() ); }
環(huán)境處理
為了讓 Dusk 使用自己的環(huán)境文件來運行測試,你需要在項目根目錄創(chuàng)建一個 .env.dusk.{environment}
文件。簡單的說,如果你想用 local
環(huán)境來運行 dusk
命令,你需要創(chuàng)建一個 .env.dusk.local
文件。
運行測試的時候,Dusk 會備份你的 .env
文件并且重命名你的 Dusk 環(huán)境文件為 .env
。當測試結(jié)束后,它會恢復(fù)你的 .env
文件。
創(chuàng)建瀏覽器
讓我們先來寫一個測試用例,這個例子可以驗證我們是否能夠登錄系統(tǒng)。生成測試例子之后,我們可以修改它并讓它可以跳轉(zhuǎn)到登錄界面,輸入登錄信息之后,點擊「登錄」按鈕。我們通過 browse
方法來創(chuàng)建一個瀏覽器實例:
<?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
方法接收了一個回調(diào)參數(shù)。Dusk 會自動將這個瀏覽器實例注入到回調(diào)過程中,而且這個瀏覽器實例可以和你的應(yīng)用進行交互和斷言。
{tip} 這個測試例子可以用來測試
make:auth
命令生成的登錄界面。
創(chuàng)建多個瀏覽器
有時候你可能需要多個瀏覽器才能正確的進行測試。例如,使用多個瀏覽器測試通過 websockets 進行通訊的在線聊天頁面。想要創(chuàng)建多個瀏覽器,需要在 browse
方法的回調(diào)中,用名字來區(qū)分瀏覽器實例,然后傳給回調(diào)去「申請」多個瀏覽器實例:
$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'); });
改變?yōu)g覽器窗口大小
你可以使用 resize
方法去調(diào)整瀏覽器的窗口大?。?/p>
$browser->resize(1920, 1080);
maximize
方法可以將瀏覽器窗口最大化:
$browser->maximize();
瀏覽器宏
如果你想定義一個可以在各種測試中重復(fù)使用的自定義瀏覽器方法,可以在 Browser
類中使用 macro
方法。通常,你應(yīng)該從 服務(wù)提供者 的 boot
方法中調(diào)用它:
<?php namespace App\Providers; use Laravel\Dusk\Browser; use Illuminate\Support\ServiceProvider; 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
方法接收一個名稱作為第一個參數(shù),第二個參數(shù)則是一個閉包。 當調(diào)用瀏覽器宏作為一個 Browser
的實現(xiàn)的方法時,瀏覽器宏的閉包將會執(zhí)行:
$this->browse(function ($browser) use ($user) { $browser->visit('/pay') ->scrollToElement('#credit-card-details') ->assertSee('Enter Credit Card Details'); });
認證
你可能經(jīng)常會測試一些需要認證的頁面。你可以使用 Dusk 的 loginAs
方法來避免每個測試都去登陸頁面登陸一次。 loginAs
可以使用用戶 ID 或者用戶模型實例:
$this->browse(function ($first, $second) { $first->loginAs(User::find(1)) ->visit('/home'); });
{note} 使用
loginAs
方法后,該用戶的 session 將會持久化的供其他測試用例使用。
數(shù)據(jù)庫遷移
就像上面的認證例子一樣,當你的測試用例需要遷移的時候,你不應(yīng)該使用 RefreshDatabase
trait。 RefreshDatabase
trait 使用了不適用于 HTTP 請求的數(shù)據(jù)庫事務(wù)。取而代之,我們要用 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 測試最困難的部分之一。隨著時間推移,前端的更改可能會導(dǎo)致類似以下的 CSS 選擇器中斷測試:
// HTML... <button>Login</button> // Test... $browser->click('.login-page .container div > button');
Dusk 選擇器讓你專注于編寫有效的測試,而不是去記憶 CSS 選擇器。要定義一個選擇器,只需在你的 HTML 元素中添加一個 dusk
屬性。然后,在選擇器前面添加 @
去操作 Dusk 測試中的附加元素:
// HTML... <button dusk="login-button">Login</button> // Test... $browser->click('@login-button');
點擊鏈接
要點擊鏈接的話,你可以在瀏覽器實例上使用 clickLink
方法。clickLink
方法將會點擊指定顯示文本的鏈接:
$browser->clickLink($linkText);
{注意} 這個方法可以與 jQuery 進行交互。如果頁面上沒有 jQuery,Dusk 會自動將其注入頁面,保證在測試的期間可用。
文本、值 & 屬性
檢索和設(shè)置值
Dusk 提供了幾種與當前顯示文本,值和屬性進行交互的方法。例如,要獲取與指定選擇器匹配的元素的「值」,請使用 value
方法:
// 檢索值... $value = $browser->value('selector'); // 設(shè)置值... $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 選擇器做為一個參數(shù),但這并不是強制要求的。如果沒有提供 CSS 選擇器, Dusk 會搜索與 name
屬性相同的 input 。如果還是沒有找到,Dusk 會嘗試查找傳入值與 name
屬性相同的 textarea
。
要想將文本附加到一個字段之后而且不清除其內(nèi)容, 你可以使用 append
方法:
$browser->type('tags', 'foo') ->append('tags', ', bar, baz');
你可以使用 clear
方法清除輸入值:
$browser->clear('email');
下拉菜單
需要在下拉菜單中選擇值,你可以使用 select
方法。 類似于 type
方法, select
方法并不是一定要傳入 CSS 選擇器。 當使用 select
方法時,你應(yīng)該傳遞選項實際的值而不是它的顯示文本:
$browser->select('size', 'Large');
你也可以通過省略第二個參數(shù)來隨機選擇一個選項:
$browser->select('size');
復(fù)選框
使用「check」 復(fù)選框時,你可以使用 check
方法。 像其他許多與 input 相關(guān)的方法,并不是必須傳入 CSS 選擇器。 如果準確的選擇器無法找到的時候,Dusk 會搜索能夠與 name
屬性匹配的復(fù)選框:
$browser->check('terms');$browser->uncheck('terms');
單選按鈕
使用 「select」中單選按鈕選項時,你可以使用 radio
這個方法。 像很多其他的與輸入相關(guān)的方法一樣, 它也并不是必須傳入 CSS 選擇器。如果準確的選擇器無法被找到的時候, Dusk 會搜索能夠與 name
屬性或者 value
屬性相匹配的單選按鈕:
$browser->radio('version', 'php7');
附件
attach
方法可以附加一個文件到 file
input 元素中。 像很多其他的與輸入相關(guān)的方法一樣,他也并不是必須傳入 CSS 選擇器。如果準確的選擇器沒有被找到的時候, Dusk 會搜索與 name
屬性匹配的文件輸入框:
$browser->attach('photo', __DIR__.'/photos/me.png');
{注意} attach 方法需要使用 PHP
Zip
擴展,你的服務(wù)器必須安裝了此擴展。
使用鍵盤
keys
方法讓你可以再指定元素中輸入比 type
方法更加復(fù)雜的輸入序列。例如,你可以在輸入值的同時按下按鍵。在這個例子中,輸入 taylor
時, shift
鍵也同時被按下。當 taylor
輸入完之后, 將會輸入 otwell
而不會按下任何按鍵:
$browser->keys('selector', ['{shift}', 'taylor'], 'otwell');
你甚至可以在你的應(yīng)用中選中某個元素之后按下「快捷鍵」:
$browser->keys('.app', ['{command}', 'j']);
{提示} 所有包在
{}
中的鍵盤按鍵, 都對應(yīng)定義于Facebook\WebDriver\WebDriverKeys
類中,你可以在 GitHub 中找到。
使用鼠標
點擊元素
click
方法可用于「點擊」與給定選擇器匹配的元素:
$browser->click('.selector');
鼠標懸停
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);
JavaScript 對話框
Dusk 提供了幾種與 JavaScript 對話框交互的方法:
// 等待對話框顯示: $browser->waitForDialog($seconds = null); // 斷言對話框已經(jīng)顯示,并且其消息與給定值匹配: $browser->assertDialogOpened('value'); // 在打開的 JavaScript 提示對話框中輸入給定值: $browser->typeInDialog('Hello World');
通過點擊確定按鈕關(guān)閉打開的 JavaScript 對話框:
$browser->acceptDialog();
通過點擊取消按鈕關(guān)閉打開的 JavaScript 對話框(僅對確認對話框有效):
$browser->dismissDialog();
選擇器作用范圍
有時可能希望在給定的選擇器范圍內(nèi)執(zhí)行多個操作。比如,可能想要斷言表格中存在某些文本,然后點擊表格中的一個按鈕??梢允褂?with
方法實現(xiàn)此需求?;卣{(diào)函數(shù)內(nèi)所有被執(zhí)行的操作都被限定在原始的選擇器上:
$browser->with('.table', function ($table) { $table->assertSee('Hello World') ->clickLink('Delete'); });
等待元素
在測試大面積使用 JavaScript 的應(yīng)用時,在進行測試之前,經(jīng)常需要「等待」指定元素或數(shù)據(jù)可用。Dusk 使之更容易。使用一系列方法,可以等到頁面元素可用,甚至給定的 JavaScript 表達式執(zhí)行結(jié)果為 true
。
等待
如果需要測試暫停指定的毫秒數(shù),可以使用 pause
方法:
$browser->pause(1000);
等待選擇器
waitFor
方法可以用于暫停執(zhí)行測試,直到頁面上與給定 CSS 選擇器匹配的元素被顯示。默認情況下,將在暫停超過 5 秒后拋出異常。如果有必要,可以傳遞自定義超時時長作為其第二個參數(shù):
// 等待選擇器 5 秒時間... $browser->waitFor('.selector'); // 等待選擇器 1 秒時間... $browser->waitFor('.selector', 1);
還可以等待指定選擇器從頁面消失:
$browser->waitUntilMissing('.selector'); $browser->waitUntilMissing('.selector', 1);
選擇器可用時限定作用域范圍
偶爾可能希望等待選擇器然后與其互動。例如,可能希望等待模態(tài)窗口可用,然后點擊模態(tài)窗口的「確定」按鈕。 whenAvailable
方法能夠用于這種情況。給定回調(diào)內(nèi)的所有要執(zhí)行的元素操作都將被限定在起始選擇器上:
$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'); // 等待給定鏈接 2 秒時間... $browser->waitForLink('Create', 1);
等待頁面跳轉(zhuǎn)
在給出類似 $browser->assertPathIs('/home')
路徑斷言時,如果 window.location.pathname
被異步更新,斷言就會失敗??梢允褂?waitForLocation
方法等待頁面跳轉(zhuǎn)到給定路徑:
$browser->waitForLocation('/secret');
還可以等待被命名的路由跳轉(zhuǎn):
$browser->waitForRoute($routeName, $parameters);
等待頁面重新加載
如果要在頁面重新加載后斷言,可以使用 waitForReload
方法:
$browser->click('.some-action') ->waitForReload() ->assertSee('something');
等待 JavaScript 表達式
有時會希望暫停執(zhí)行測試,直到給定的 JavaScript 表達式執(zhí)行結(jié)果為 true
??梢允褂?waitUntil
方法輕易地達成此目的。傳送一個表達式給此方法,不需要包含 return
關(guān)鍵字或者結(jié)束分號:
//等待表達式為 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');
等待回調(diào)
Dusk 的許多「等待」方法都依賴底層的 waitUsing
方法??梢灾苯邮褂么朔椒▉淼却o定的回調(diào)返回 true
。 waitUsing
方法接受等待的最大秒數(shù),閉包執(zhí)行的間隔時長,被執(zhí)行的閉包,還有可靠的失敗消息:
$browser->waitUsing(10, 1, function () use ($something) { return $something->isReady(); },"Something wasn't ready in time.");
做出 Vue 斷言
Dusk 還允許你對 Vue 組件數(shù)據(jù)的狀態(tài)作出斷言。例如,假設(shè)您的應(yīng)用程序包含以下 Vue 組件:
// HTML... <profile dusk="profile-component"></profile> // 定義組件... Vue.component('profile', { template: '<div>{{ user.name }}</div>', data: function () { return { user: { name: 'Taylor' } }; } });
你可以在 Vue 組件的狀態(tài)上作出如下斷言:
/** * 一個簡單的 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
assertCookieMissing
assertCookieValue
assertPlainCookieValue
assertSee
assertDontSee
assertSeeIn
assertDontSeeIn
assertSourceHas
assertSourceMissing
assertSeeLink
assertDontSeeLink
assertInputValue
assertInputValueIsNot
assertChecked
assertNotChecked
assertRadioSelected
assertRadioNotSelected
assertSelected
assertNotSelected
assertSelectHasOptions
assertSelectMissingOptions
assertSelectHasOption
assertValue
assertVisible
assertPresent
assertMissing
assertDialogOpened
assertEnabled
assertDisabled
assertFocused
assertNotFocused
assertVue
assertVueIsNot
assertVueContains
assertVueDoesNotContain
assertTitle
斷言網(wǎng)頁標題匹配指定的文本:
$browser->assertTitle($title);
assertTitleContains
斷言網(wǎng)頁標題包含指定的文本:
$browser->assertTitleContains($title);
assertUrlIs
斷言當前 URL (不帶查詢字符串) 匹配指定的字符串:
$browser->assertUrlIs($url);
assertSchemeIs
斷言當前 URL 匹配與給定的字符串匹配:
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
斷言當前 URL 匹配與給定的字符串不匹配:
$browser->assertSchemeIsNot($scheme);
assertHostIs
斷言當前 URL 的 host 與給定的值匹配:
$browser->assertHostIs($host);
assertHostIsNot
斷言當前 URL 的 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
斷言存在指定的查詢字符串參數(shù):
$browser->assertQueryStringHas($name);
斷言指定的查詢字符串參數(shù)存在,并且該參數(shù)的值為指定的值:
$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
斷言不存在指定的查詢字符串參數(shù):
$browser->assertQueryStringMissing($name);
assertFragmentIs
斷言目前的分片符合指定的分片:
$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
斷言目前的分片以指定的分片開頭:
$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
斷言目前的分片不符合指定的分片:
$browser->assertFragmentIsNot('anchor');
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
斷言選擇器范圍內(nèi)存在指定的文本:
$browser->assertSeeIn($selector, $text);
assertDontSeeIn
斷言選擇器范圍內(nèi)不存在指定的文本:
$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
斷言指定的復(fù)選框被選中:
$browser->assertChecked($field);
assertNotChecked
斷言指定的復(fù)選框未選中:
$browser->assertNotChecked($field);
assertRadioSelected
斷言指定的單選按鈕被選?。?/p>
$browser->assertRadioSelected($field, $value);
assertRadioNotSelected
斷言指定的單選按鈕未被選?。?/p>
$browser->assertRadioNotSelected($field, $value);
assertSelected
斷言下拉框被選取指定的值:
$browser->assertSelected($field, $value);
assertNotSelected
斷言下拉框未選取指定的值:
$browser->assertNotSelected($field, $value);
assertSelectHasOptions
斷言可選到指定數(shù)組中的值:
$browser->assertSelectHasOptions($field, $values);
assertSelectMissingOptions
斷言選取的值并非指定數(shù)組中的值:
$browser->assertSelectMissingOptions($field, $values);
assertSelectHasOption
斷言可選到指定的值:
$browser->assertSelectHasOption($field, $value);
assertValue
斷言選擇器范圍內(nèi)的元素存在指定的值:
$browser->assertValue($selector, $value);
assertVisible
斷言選擇器范圍內(nèi)的元素可見:
$browser->assertVisible($selector);
assertPresent
斷言選擇器范圍內(nèi)的元素是存在的:
$browser->assertPresent($selector);
assertMissing
斷言選擇器范圍內(nèi)的元素不存在:
$browser->assertMissing($selector);
assertDialogOpened
斷言含有指定消息的 JavaScript 對話框已經(jīng)打開:
$browser->assertDialogOpened($message);
assertEnabled
斷言指定的字段是啟用的:
$browser->assertEnabled($field);
assertDisabled
斷言指定的字段是停用的:
$browser->assertDisabled($field);
assertFocused
斷言焦點在于指定的字段:
$browser->assertFocused($field);
assertNotFocused
斷言焦點不在指定的字段:
$browser->assertNotFocused($field);
assertVue
斷言 Vue 組件數(shù)據(jù)的屬性匹配指定的值:
$browser->assertVue($property, $value, $componentSelector = null);
assertVueIsNot
斷言 Vue 組件數(shù)據(jù)的屬性不匹配指定的值:
$browser->assertVueIsNot($property, $value, $componentSelector = null);
assertVueContains
斷言 Vue 組件數(shù)據(jù)的屬性是一個數(shù)組,并且該數(shù)組包含指定的值:
$browser->assertVueContains($property, $value, $componentSelector = null);
assertVueDoesNotContain
斷言 Vue 組件數(shù)據(jù)的屬性是一個數(shù)組,并且該數(shù)組不包含指定的值:
$browser->assertVueDoesNotContain($property, $value, $componentSelector = null);
頁面
有時候,需要測試一系列復(fù)雜的動作,這會使得測試代碼難以閱讀和理解。通過頁面可以定義出語義化的動作,然后在指定頁面中可以使用單個方法。頁面還可以定義應(yīng)用或單個頁面通用選擇器的快捷方式。
生成頁面
dusk:page
Artisan 命令可以生成頁面對象。所有的頁面對象都位于 tests/Browser/Pages
目錄:
php artisan dusk:page Login
配置頁面
頁面默認擁有 3 個方法: url
, assert
和 elements
。 在這里我們先詳述 url
和 assert
方法, elements
方法將會 選擇器簡寫 中詳述。
url
方法
url
方法應(yīng)該返回表示頁面 URL 的路徑。 Dusk 將會在瀏覽器中使用這個 URL 來導(dǎo)航到具體頁面:
/** * 獲得頁面 URL 路徑。 * * @return string */ public function url(){ return '/login'; }
assert
方法
assert
方法可以作出任何斷言來驗證瀏覽器是否在指定頁面上。這個方法并不是必須的。你可以根據(jù)你自己的需求來做出這些斷言。這些斷言會在你導(dǎo)航到這個頁面的時候自動執(zhí)行:
/** * 斷言瀏覽器當前處于指定頁面。 * * @return void */ public function assert(Browser $browser){ $browser->assertPathIs($this->url()); }
導(dǎo)航至頁面
一旦頁面配置好之后,你可以使用 visit
方法導(dǎo)航至頁面:
use Tests\Browser\Pages\Login;$browser->visit(new Login);
有時候,你可能已經(jīng)在指定頁面了,你需要的只是「加載」當前頁面的選擇器和方法到當前測試中來。常見的例子有:當你按下一個按鈕的時候,你會被重定向至指定頁面,而不是直接導(dǎo)航至指定頁面。在這種情況下,你需要使用 on
方法來加載頁面:
use Tests\Browser\Pages\CreatePlaylist;$browser->visit('/dashboard') ->clickLink('Create Playlist') ->on(new CreatePlaylist) ->assertSee('@create');
選擇器簡寫
elements
方法允許你為頁面中的任何 CSS 選擇器定義簡單易記的簡寫。例如,讓我們?yōu)閼?yīng)用登錄頁中的 email
輸入框定義一個簡寫:
/** * 獲取頁面的元素簡寫。 * * @return array */ public function elements(){ return [ '@email' => 'input[name=email]', ]; }
現(xiàn)在你可以用這個簡寫來代替之前在頁面中使用的完整 CSS 選擇器:
$browser->type('@email', 'taylor@laravel.com');
全局的選擇器簡寫
安裝 Dusk 之后,Page
基類存放在你的 tests/Browser/Pages
目錄。該類中包含一個 siteElements
方法,這個方法可以用來定義全局的選擇器簡寫,這樣在你應(yīng)用中每個頁面都可以使用這些全局選擇器簡寫了:
/** * 獲取站點全局的選擇器簡寫。 * * @return array */ public static function siteElements(){ return [ '@element' => '#selector', ]; }
頁面方法
除了頁面中已經(jīng)定義的默認方法之外,你還可以定義在整個測試過程中會使用到的其他方法。例如,假設(shè)我們正在開發(fā)一個音樂管理應(yīng)用,在應(yīng)用中都可能需要一個公共的方法來創(chuàng)建列表,而不是在每一頁、每一個測試類中都重寫一遍創(chuàng)建播放列表的邏輯,這時候你可以在你的頁面類中定義一個 createPlaylist
方法:
<?php namespace Tests\Browser\Pages; use Laravel\Dusk\Browser; class Dashboard extends Page{ // 其他頁面方法... /** * 創(chuàng)建一個新的播放列表。 * * @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 的 「頁面對象」,不過它更多的是貫穿整個應(yīng)用程序中頻繁重用的 UI 和功能片斷,比如說導(dǎo)航條或信息通知彈窗。因此,組件并不會綁定于某個明確的 URL。
組件的生成
為了生成一個組件,使用 Artisan 命令 dusk:component
即可生成組件。新生成的組件位于 test/Browser/Components
目錄中:
php artisan dusk:component DatePicker
如上所示,這是生成一個 “日期選擇器”(date picker) 組件的示例,這個組件可能會貫穿使用在你應(yīng)用程序的許多頁面中。在整個測試套件的大量測試頁面中,手動編寫日期選擇的瀏覽器自動化邏輯會非常麻煩。 更方便的替代辦法是,定義一個表示日期選擇器的 Dusk 組件,然后把自動化邏輯封裝在該組件內(nèi):
<?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 $day * @return void */ public function selectDate($browser, $month, $day) { $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'); }); } }
持續(xù)集成
CircleCI
如果使用 CircleCI 運行 Dusk 測試,則可以參考此配置文件作為起點。與 TravisCI 一樣,我們將使用 php artisan serve
命令啟動 PHP 的內(nèi)置 Web 服務(wù)器:
version: 2jobs: 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 中運行 Dusk 測試,需要在你的 Codeship 項目中添加以下命令。當然,這些命令只是作為范例,你可以根據(jù)需要隨意添加額外的命令:
phpenv local 7.2 cp .env.testing .env mkdir -p ./bootstrap/cache composer install --no-interaction --prefer-dist php artisan key:generate nohup bash -c "php artisan serve 2>&1 &" && sleep 5 php artisan dusk
Heroku CI
在 Heroku CI 中運行 Dusk 測試時,請將下列 Google Chrome 構(gòu)建包和腳本添加到你的 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 不是圖形環(huán)境,因此我們需要采取一些額外的步驟來啟動 Chrome 瀏覽器。此外,我們將使用 php artisan serve
啟動 PHP 的內(nèi)置 Web 服務(wù)器:
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 before_script: - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & - php artisan serve & script: - php artisan dusk
在 .env.testing
文件中,調(diào)整 APP_URL
的值:
APP_URL=http://127.0.0.1:8000