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