Laravel Dusk
簡介 (Introduction)
[!WARNING] > Pest 4 現在包含了自動化瀏覽器測試功能,相比 Laravel Dusk 提供了顯著的性能和可用性改進。對於新項目,我們建議使用 Pest 進行瀏覽器測試。
Laravel Dusk 提供了一個表達性強、易於使用的瀏覽器自動化和測試 API。默認情況下,Dusk 不需要您在本地電腦上安裝 JDK 或 Selenium。相反,Dusk 使用一個獨立的 ChromeDriver 安裝。不過,您也可以自由使用任何其他兼容 Selenium 的驅動程序。
安裝 (Installation)
開始使用之前,您應該安裝 Google Chrome 並將 laravel/dusk Composer 依賴項添加到您的項目中:
composer require laravel/dusk --dev
[!WARNING] 如果您手動註冊 Dusk 的服務提供者,千萬不要在生產環境中註冊它,因為這樣可能會導致任意用戶能夠驗證您的應用程序。
安裝 Dusk 包後,執行 dusk:install Artisan 命令。dusk:install 命令將創建一個 tests/Browser 目錄、一個示例 Dusk 測試和為您的操作系統安裝 Chrome Driver 二進制文件:
php artisan dusk:install
接下來,在應用程序的 .env 文件中設置 APP_URL 環境變數。這個值應該與您在瀏覽器中訪問應用程序的 URL 相匹配。
[!NOTE] 如果您使用 Laravel Sail 來管理本地開發環境,請同時查閱 Sail 文檔中有關 配置和運行 Dusk 測試 的部分。
管理 ChromeDriver 安裝 (Managing Chromedriver Installations)
如果您想安裝與 Laravel Dusk 通過 dusk:install 命令安裝的不同版本的 ChromeDriver,您可以使用 dusk:chrome-driver 命令:
# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver
# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 86
# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all
# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect
[!WARNING] Dusk 要求
chromedriver二進制文件必須是可執行的。如果您在運行 Dusk 時遇到問題,您應該使用以下命令確保二進制文件是可執行的:chmod -R 0755 vendor/laravel/dusk/bin/。
使用其他瀏覽器 (Using Other Browsers)
默認情況下,Dusk 使用 Google Chrome 和獨立的 ChromeDriver 安裝來運行您的瀏覽器測試。不過,您可以啟動自己的 Selenium 服務器並針對任何您希望的瀏覽器運行測試。
開始使用,打開 tests/DuskTestCase.php 文件,這是您應用程序的基礎 Dusk 測試用例。在此文件中,您可以移除對 startChromeDriver 方法的調用。這將停止 Dusk 自動啟動 ChromeDriver:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
接下來,您可以修改 driver 方法以連接到您選擇的 URL 和端口。此外,您可以修改應傳遞給 WebDriver 的 "desired capabilities":
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}
開始使用 (Getting Started)
生成測試 (Generating Tests)
要生成 Dusk 測試,使用 dusk:make Artisan 命令。生成的測試將放在 tests/Browser 目錄中:
php artisan dusk:make LoginTest
在每次測試後重置數據庫 (Resetting The Database After Each Test)
您編寫的大多數測試都會與從應用程序數據庫檢索數據的頁面進行交互;但是,您的 Dusk 測試不應該使用 RefreshDatabase trait。RefreshDatabase trait 利用數據庫事務,這將不適用或不可用於 HTTP 請求。相反,您有兩個選項:DatabaseMigrations trait 和 DatabaseTruncation trait。
使用數據庫遷移 (Reset Migrations)
DatabaseMigrations trait 將在每次測試前運行您的數據庫遷移。但是,為每次測試刪除和重新創建數據庫表通常比截斷表要慢:
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
pest()->use(DatabaseMigrations::class);
//
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
//
}
[!WARNING] 執行 Dusk 測試時不能使用 SQLite 內存數據庫。由於瀏覽器在其自己的進程中執行,它無法訪問其他進程的內存數據庫。
使用數據庫截斷 (Reset Truncation)
DatabaseTruncation trait 將在第一次測試時遷移您的數據庫以確保您的數據庫表已正確創建。但是,在隨後的測試中,數據庫的表將被簡單地截斷 - 比重新運行所有數據庫遷移提供了速度提升:
<?php
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
pest()->use(DatabaseTruncation::class);
//
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseTruncation;
//
}
默認情況下,此 trait 將截斷除 migrations 表外的所有表。如果您想自定義應截斷的表,您可以在測試類上定義 $tablesToTruncate 屬性:
[!NOTE] 如果您使用 Pest,您應該在基礎
DuskTestCase類上或您的測試文件擴展的任何類上定義屬性或方法。
/**
* Indicates which tables should be truncated.
*
* @var array
*/
protected $tablesToTruncate = ['users'];
或者,您可以在測試類上定義 $exceptTables 屬性以指定應從截斷中排除的表:
/**
* Indicates which tables should be excluded from truncation.
*
* @var array
*/
protected $exceptTables = ['users'];
要指定應截斷其表的數據庫連接,您可以在測試類上定義 $connectionsToTruncate 屬性:
/**
* Indicates which connections should have their tables truncated.
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];
如果您想在執行數據庫截斷前或後執行代碼,您可以在測試類上定義 beforeTruncatingDatabase 或 afterTruncatingDatabase 方法:
/**
* Perform any work that should take place before the database has started truncating.
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* Perform any work that should take place after the database has finished truncating.
*/
protected function afterTruncatingDatabase(): void
{
//
}
運行測試 (Running Tests)
要運行您的瀏覽器測試,執行 dusk Artisan 命令:
php artisan dusk
如果您上次運行 dusk 命令時出現測試失敗,您可以使用 dusk:fails 命令首先重新運行失敗的測試來節省時間:
php artisan dusk:fails
dusk 命令接受 Pest / PHPUnit 測試運行器通常接受的任何參數,例如允許您只運行給定 group 的測試:
php artisan dusk --group=foo
[!NOTE] 如果您使用 Laravel Sail 來管理本地開發環境,請查閱 Sail 文檔中有關 配置和運行 Dusk 測試 的部分。
手動啟動 ChromeDriver (Manually Starting Chromedriver)
默認情況下,Dusk 將自動嘗試啟動 ChromeDriver。如果這對您的特定系統不起作用,您可以在運行 dusk 命令之前手動啟動 ChromeDriver。如果您選擇手動啟動 ChromeDriver,您應該註釋掉 tests/DuskTestCase.php 文件中的以下行:
/**
* Prepare for Dusk test execution.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
此外,如果您在 9515 以外的端口上啟動 ChromeDriver,您應該修改同一類的 driver 方法以反映正確的端口:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:9515', DesiredCapabilities::chrome()
);
}
環境處理 (Environment Handling)
要強制 Dusk 在運行測試時使用其自己的環境文件,請在項目根目錄中創建 .env.dusk.{environment} 文件。例如,如果您將從 local 環境啟動 dusk 命令,您應該創建 .env.dusk.local 文件。
運行測試時,Dusk 將備份您的 .env 文件並將您的 Dusk 環境重命名為 .env。測試完成後,您的 .env 文件將被恢復。
瀏覽器基礎 (Browser Basics)
創建瀏覽器 (Creating Browsers)
開始使用,讓我們編寫一個測試來驗證我們可以登錄到應用程序。生成測試後,我們可以修改它以導航到登錄頁面、輸入一些憑據並點擊 "登錄" 按鈕。要創建瀏覽器實例,您可以在 Dusk 測試中調用 browse 方法:
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
pest()->use(DatabaseMigrations::class);
test('basic example', function () {
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
});
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* A basic browser test example.
*/
public function test_basic_example(): void
{
$user = User::factory()->create([
'email' => 'taylor@laravel.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
}
}
正如您在上面的示例中所看到的,browse 方法接受一個閉包。瀏覽器實例將由 Dusk 自動傳遞到此閉包,是用於與應用程序交互和進行斷言的主要對象。
創建多個瀏覽器 (Creating Multiple Browsers)
有時您可能需要多個瀏覽器才能正確執行測試。例如,測試與 websocket 交互的聊天屏幕可能需要多個瀏覽器。要創建多個瀏覽器,只需將更多瀏覽器參數添加到提供給 browse 方法的閉包的簽名中:
$this->browse(function (Browser $first, Browser $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');
});
導航 (Navigation)
visit 方法可用於導航到應用程序中的給定 URI:
$browser->visit('/login');
您可以使用 visitRoute 方法導航到 命名路由:
$browser->visitRoute($routeName, $parameters);
您可以使用 back 和 forward 方法"後退"和"前進":
$browser->back();
$browser->forward();
您可以使用 refresh 方法刷新頁面:
$browser->refresh();
調整瀏覽器窗口大小 (Resizing Browser Windows)
您可以使用 resize 方法調整瀏覽器窗口的大小:
$browser->resize(1920, 1080);
maximize 方法可用於最大化瀏覽器窗口:
$browser->maximize();
fitContent 方法將調整瀏覽器窗口大小以匹配其內容的大小:
$browser->fitContent();
當測試失敗時,Dusk 將自動調整瀏覽器大小以在拍攝屏幕截圖前適應內容。您可以通過在測試中調用 disableFitOnFailure 方法來禁用此功能:
$browser->disableFitOnFailure();
您可以使用 move 方法將瀏覽器窗口移動到屏幕上的不同位置:
$browser->move($x = 100, $y = 100);
瀏覽器宏 (Browser Macros)
如果您想定義可在各種測試中重複使用的自定義瀏覽器方法,您可以在 Browser 類上使用 macro 方法。通常,您應該從 服務提供者的 boot 方法中調用此方法:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;
class DuskServiceProvider extends ServiceProvider
{
/**
* Register Dusk's browser macros.
*/
public function boot(): void
{
Browser::macro('scrollToElement', function (string $element = null) {
$this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");
return $this;
});
}
}
macro 函數接受名稱作為第一個參數,閉包作為第二個參數。宏的閉包將在作為 Browser 實例上的方法調用宏時執行:
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#credit-card-details')
->assertSee('Enter Credit Card Details');
});
身份驗證 (Authentication)
通常,您將測試需要身份驗證的頁面。您可以使用 Dusk 的 loginAs 方法以避免在每次測試期間與應用程序的登錄屏幕交互。loginAs 方法接受與可認證模型相關聯的主鍵或可認證模型實例:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});
[!WARNING] 使用
loginAs方法後,用戶會話將為文件中的所有測試保持。
Cookie
您可以使用 cookie 方法獲取或設置加密 cookie 的值。默認情況下,Laravel 創建的所有 cookie 都是加密的:
$browser->cookie('name');
$browser->cookie('name', 'Taylor');
您可以使用 plainCookie 方法獲取或設置未加密 cookie 的值:
$browser->plainCookie('name');
$browser->plainCookie('name', 'Taylor');
您可以使用 deleteCookie 方法刪除給定的 cookie:
$browser->deleteCookie('name');
執行 JavaScript (Executing Javascript)
您可以使用 script 方法在瀏覽器中執行任意 JavaScript 語句:
$browser->script('document.documentElement.scrollTop = 0');
$browser->script([
'document.body.scrollTop = 0',
'document.documentElement.scrollTop = 0',
]);
$output = $browser->script('return window.location.pathname');
拍攝屏幕截圖 (Taking A Screenshot)
您可以使用 screenshot 方法拍攝屏幕截圖並使用給定的文件名存儲它。所有屏幕截圖將存儲在 tests/Browser/screenshots 目錄中:
$browser->screenshot('filename');
responsiveScreenshots 方法可用於在各種斷點處拍攝一系列屏幕截圖:
$browser->responsiveScreenshots('filename');
screenshotElement 方法可用於拍攝頁面上特定元素的屏幕截圖:
$browser->screenshotElement('#selector', 'filename');
將控制台輸出存儲到磁盤 (Storing Console Output To Disk)
您可以使用 storeConsoleLog 方法將當前瀏覽器的控制台輸出寫入磁盤,並使用給定的文件名。控制台輸出將存儲在 tests/Browser/console 目錄中:
$browser->storeConsoleLog('filename');
將頁面源存儲到磁盤 (Storing Page Source To Disk)
您可以使用 storeSource 方法將當前頁面的源寫入磁盤,並使用給定的文件名。頁面源將存儲在 tests/Browser/source 目錄中:
$browser->storeSource('filename');
與元素互動 (Interacting With Elements)
Dusk 選擇器 (Dusk Selectors)
為與元素交互選擇好的 CSS 選擇器是編寫 Dusk 測試最難的部分之一。隨著時間的推移,前端更改可能會導致 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');
如果需要,您可以通過 selectorHtmlAttribute 方法自定義 Dusk 選擇器使用的 HTML 屬性。通常,此方法應從應用程序 AppServiceProvider 的 boot 方法調用:
use Laravel\Dusk\Dusk;
Dusk::selectorHtmlAttribute('data-dusk');
文本、值與屬性 (Text Values And Attributes)
獲取與設置值 (Retrieving Setting Values)
Dusk 提供了數種方法,用於與頁面上元素的當前值、顯示文字與屬性進行互動。例如,要取得與給定 CSS 或 Dusk 選擇器相匹配的元素的 "值",可使用 value 方法:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');
您可以使用 inputValue 方法來獲取具有指定欄位名稱的輸入元素的 "值":
$value = $browser->inputValue('field');
獲取文本 (Retrieving Text)
text 方法可用於檢索與指定選擇器匹配的元素的顯示文本:
$text = $browser->text('selector');
獲取屬性 (Retrieving Attributes)
最後,attribute 方法可用於檢索匹配指定選擇器的元素的屬性值:
$attribute = $browser->attribute('selector', 'value');
與表單互動 (Interacting With Forms)
輸入值 (Typing Values)
Dusk 提供了多種與表單和輸入元素互動的方法。首先,我們先看一個向輸入欄位輸入文字的範例:
$browser->type('email', 'taylor@laravel.com');
注意:雖然此方法在必要時可接受一個選擇器,但並不要求您一定在 type 方法中傳入 CSS 選擇器。如果沒有提供 CSS 選擇器,Dusk 會尋找具有給定 name 屬性的 input 或 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');
下拉選單 (Dropdowns)
要從 select 元素中選擇一個可用的值,您可以使用 select 方法。與 type 方法一樣,select 方法並不需要完整的 CSS 選擇器。當向 select 方法傳遞值時,應該傳入內部 option 的 value,而非顯示文字:
$browser->select('size', 'Large');
您也可以省略第二個參數來隨機選擇一個選項:
$browser->select('size');
將一個陣列作為 select 方法的第二個參數,可以讓該方法選擇多個選項:
$browser->select('categories', ['Art', 'Music']);
複選框 (Checkboxes)
要勾選一個 checkbox 輸入欄位,您可以使用 check 方法。與其他輸入相關方法類似,不需要完整的 CSS 選擇器。如果找不到 CSS 選擇器匹配,Dusk 會尋找具有匹配 name 屬性的 checkbox:
$browser->check('terms');
uncheck 方法可用於取消勾選 checkbox 輸入:
$browser->uncheck('terms');
單選框 (Radio Buttons)
要選擇一個 radio 輸入選項,您可以使用 radio 方法。與其他輸入相關方法類似,並不需要完整的 CSS 選擇器。如果找不到 CSS 匹配,Dusk 會尋找具有匹配 name 和 value 屬性的 radio 元素:
$browser->radio('size', 'large');
附加檔案 (Attaching Files)
attach 方法可用於向 file 輸入元素附加檔案。與其他輸入相關的方法一樣,不需要完整的 CSS 選擇器。如果找不到 CSS 選擇的匹配,Dusk 會尋找具有相應 name 屬性的 file 輸入元素:
$browser->attach('photo', __DIR__.'/photos/mountains.png');
[!WARNING] 附加檔案功能需要在您的伺服器上安裝並啟用
ZipPHP 擴充功能。
按鈕操作 (Pressing Buttons)
press 方法可用於點擊頁面上的按鈕元素。傳遞給 press 方法的參數可以是按鈕的顯示文字或 CSS / Dusk 選擇器:
$browser->press('Login');
在提交表單時,許多應用會在按下提交按鈕後禁用該按鈕,直到 HTTP 請求完成並重新啟用按鈕。要按下按鈕並等待按鈕重新啟用,您可以使用 pressAndWaitFor 方法:
// Press the button and wait a maximum of 5 seconds for it to be enabled...
$browser->pressAndWaitFor('Save');
// Press the button and wait a maximum of 1 second for it to be enabled...
$browser->pressAndWaitFor('Save', 1);
點擊連結 (Clicking Links)
要點擊鏈結,您可以在瀏覽器實例上使用 clickLink 方法。clickLink 方法會點擊具有指定顯示文字的鏈結:
$browser->clickLink($linkText);
您可以使用 seeLink 方法判斷頁面上是否可見具有指定顯示文字的鏈結:
if ($browser->seeLink($linkText)) {
// ...
}
[!WARNING] These methods interact with jQuery. If jQuery is not available on the page, Dusk will automatically inject it into the page so it is available for the test's duration.
使用鍵盤 (Using The Keyboard)
keys 方法允許您對指定元素提供比 type 方法更複雜的輸入序列。例如,您可以指示 Dusk 在輸入值時按住修飾鍵。在此示例中,輸入 taylor 時會按住 shift 鍵,之後輸入 swift 時則不會按任何修飾鍵:
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
另一個 keys 方法的實用案例是向應用程式的主要 CSS 選擇器發送「鍵盤快捷鍵」組合:
$browser->keys('.app', ['{command}', 'j']);
[!NOTE] 所有修飾鍵(例如
{command})皆以{}包裹,並對應Facebook\\WebDriver\\WebDriverKeys類中定義的常數,可在 GitHub 上找到。
鍵盤進階交互 (Fluent Keyboard Interactions)
Dusk 也提供 withKeyboard 方法,允許您透過 Laravel\Dusk\Keyboard 類順暢地執行複雜的鍵盤互動操作。Keyboard 類提供 press、release、type 與 pause 方法:
use Laravel\Dusk\Keyboard;
$browser->withKeyboard(function (Keyboard $keyboard) {
$keyboard->press('c')
->pause(1000)
->release('c')
->type(['c', 'e', 'o']);
});
鍵盤巨集 (Keyboard Macros)
如果您想定義可在測試套件中重複使用的自定義鍵盤互動,可以使用 Keyboard 類提供的 macro 方法。通常建議在服務提供者(service provider)的 boot 方法中調用此方法:
<?php
namespace App\Providers;
use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;
class DuskServiceProvider extends ServiceProvider
{
/**
* Register Dusk's browser macros.
*/
public function boot(): void
{
Keyboard::macro('copy', function (string $element = null) {
$this->type([
OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
]);
return $this;
});
Keyboard::macro('paste', function (string $element = null) {
$this->type([
OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
]);
return $this;
});
}
}
macro 函數接受名稱作為第一個參數,閉包作為第二個參數。當在 Keyboard 實例上以方法方式呼叫該宏時,宏的閉包將被執行:
$browser->click('@textarea')
->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
->click('@another-textarea')
->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());
使用滑鼠 (Using The Mouse)
點擊元素 (Clicking On Elements)
click 方法可用於點擊匹配給定 CSS 或 Dusk 選擇器的元素:
$browser->click('.selector');
clickAtXPath 方法可用於點擊匹配給定 XPath 表達式的元素:
$browser->clickAtXPath('//div[@class = "selector"]');
clickAtPoint 方法可用於點擊相對於瀏覽器可視區域給定座標對上方的最上層元素:
$browser->clickAtPoint($x = 0, $y = 0);
doubleClick 方法可用於模擬滑鼠的雙擊操作:
$browser->doubleClick();
$browser->doubleClick('.selector');
rightClick 方法可用於模擬滑鼠的右鍵點擊操作:
$browser->rightClick();
$browser->rightClick('.selector');
clickAndHold 方法可用於模擬滑鼠按鈕被按下並保持不放。隨後呼叫 releaseMouse 方法可取消此操作並釋放滑鼠按鈕:
$browser->clickAndHold('.selector');
$browser->clickAndHold()
->pause(1000)
->releaseMouse();
controlClick 方法可用於在瀏覽器中模擬 ctrl+click 事件:
$browser->controlClick();
$browser->controlClick('.selector');
滑鼠懸停 (Mouseover)
當您需要將滑鼠移到匹配給定 CSS 或 Dusk 選擇器的元素上方時,可以使用 mouseover 方法:
$browser->mouseover('.selector');
拖放操作 (Drag Drop)
drag 方法可用於將匹配給定選擇器的元素拖動到另一個元素上:
$browser->drag('.from-selector', '.to-selector');
或者,您也可以將元素向單一方向拖動:
$browser->dragLeft('.selector', $pixels = 10);
$browser->dragRight('.selector', $pixels = 10);
$browser->dragUp('.selector', $pixels = 10);
$browser->dragDown('.selector', $pixels = 10);
最後,您可以通過指定偏移量拖動元素:
$browser->dragOffset('.selector', $x = 10, $y = 10);
JavaScript 對話框 (Javascript Dialogs)
Dusk 提供了多種方法與 JavaScript 對話框(Dialog)交互。例如,您可以使用 waitForDialog 方法等待 JavaScript 對話框出現。該方法接受一個可選數值參數,用於指定等待對話框出現的秒數:
$browser->waitForDialog($seconds = null);
assertDialogOpened 方法可用於斷言對話框已顯示並包含給定訊息:
$browser->assertDialogOpened('Dialog message');
如果 JavaScript 對話框包含提示(prompt),您可以使用 typeInDialog 方法在提示中輸入值:
$browser->typeInDialog('Hello World');
要通過點擊「OK」按鈕關閉開啟的 JavaScript 對話框,您可以調用 acceptDialog 方法:
$browser->acceptDialog();
要通過點擊「取消(Cancel)」按鈕關閉開啟的 JavaScript 對話框,您可以調用 dismissDialog 方法:
$browser->dismissDialog();
與內嵌框架(iframe)互動 (Interacting With Iframes)
如果您需要與 iframe 內的元素交互,可以使用 withinFrame 方法。傳入 withinFrame 閉包中的所有元素交互都將被作用於該指定 iframe 的上下文中:
$browser->withinFrame('#credit-card-details', function ($browser) {
$browser->type('input[name="cardnumber"]', '4242424242424242')
->type('input[name="exp-date"]', '1224')
->type('input[name="cvc"]', '123')
->press('Pay');
});
選擇器作用域 (Scoping Selectors)
有時您可能想在指定選擇器的作用域內執行多個操作。例如,您可能希望斷言某些文字僅存在於表格中,然後在該表格內點擊按鈕。您可以使用 with 方法來實現此目的。傳入 with 閉包的所有操作都會被限定在原始選擇器的範圍內:
$browser->with('.table', function (Browser $table) {
$table->assertSee('Hello World')
->clickLink('Delete');
});
有時您可能需要在當前作用域之外執行斷言。可以使用 elsewhere 與 elsewhereWhenAvailable 方法來實現這一點:
$browser->with('.table', function (Browser $table) {
// Current scope is `body .table`...
$browser->elsewhere('.page-title', function (Browser $title) {
// Current scope is `body .page-title`...
$title->assertSee('Hello World');
});
$browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
// Current scope is `body .page-title`...
$title->assertSee('Hello World');
});
});
等待元素 (Waiting For Elements)
當測試大量使用 JavaScript 的應用時,通常需要在繼續測試之前「等待」某些元素或資料可用。Dusk 讓這件事變得非常簡單。透過多種方法,您可以等待元素在頁面上變為可見,甚至等待某個 JavaScript 表達式評估為 true。
等待 (Waiting)
如果您只需要讓測試暫停指定的毫秒數,請使用 pause 方法:
$browser->pause(1000);
如果您僅在某個條件為 true 時暫停測試,請使用 pauseIf 方法:
$browser->pauseIf(App::environment('production'), 1000);
同樣地,如果您需要在某個條件不是 true 時暫停測試,可以使用 pauseUnless 方法:
$browser->pauseUnless(App::environment('testing'), 1000);
等待選擇器 (Waiting For Selectors)
waitFor 方法可用於暫停測試執行,直到匹配給定 CSS 或 Dusk 選擇器的元素出現在頁面上。預設情況下,這會在拋出例外之前最多等待五秒。如有必要,您可以將自訂超時閾值作為第二個參數傳入該方法:
// Wait a maximum of five seconds for the selector...
$browser->waitFor('.selector');
// Wait a maximum of one second for the selector...
$browser->waitFor('.selector', 1);
您也可以等待匹配指定選擇器的元素包含給定文字:
// Wait a maximum of five seconds for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World');
// Wait a maximum of one second for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World', 1);
您也可以等待匹配指定選擇器的元素從頁面上消失:
// Wait a maximum of five seconds until the selector is missing...
$browser->waitUntilMissing('.selector');
// Wait a maximum of one second until the selector is missing...
$browser->waitUntilMissing('.selector', 1);
或者,您也可以等待匹配指定選擇器的元素變為可用或被禁用:
// Wait a maximum of five seconds until the selector is enabled...
$browser->waitUntilEnabled('.selector');
// Wait a maximum of one second until the selector is enabled...
$browser->waitUntilEnabled('.selector', 1);
// Wait a maximum of five seconds until the selector is disabled...
$browser->waitUntilDisabled('.selector');
// Wait a maximum of one second until the selector is disabled...
$browser->waitUntilDisabled('.selector', 1);
在可用時限定作用域 (Scoping Selectors When Available)
有時您可能想等待一個匹配指定選擇器的元素出現,然後再與該元素交互。例如,您可能希望等待模態視窗可用,然後在該模態中按下「OK」按鈕。可以使用 whenAvailable 方法來完成此操作。傳入 whenAvailable 的閉包中執行的所有元素操作都會被限定在該選擇器的範圍內:
$browser->whenAvailable('.modal', function (Browser $modal) {
$modal->assertSee('Hello World')
->press('OK');
});
等待文字 (Waiting For Text)
waitForText 方法可用於等待頁面上顯示指定文字:
// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');
// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);
您可以使用 waitUntilMissingText 方法等待頁面上顯示的文字被移除:
// Wait a maximum of five seconds for the text to be removed...
$browser->waitUntilMissingText('Hello World');
// Wait a maximum of one second for the text to be removed...
$browser->waitUntilMissingText('Hello World', 1);
等待連結 (Waiting For Links)
waitForLink 方法可用於等待頁面上顯示指定的鏈結文字:
// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');
// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);
等待輸入欄位 (Waiting For Inputs)
waitForInput 方法可用於等待指定的輸入欄位在頁面上可見:
// Wait a maximum of five seconds for the input...
$browser->waitForInput($field);
// Wait a maximum of one second for the input...
$browser->waitForInput($field, 1);
等待頁面位置 (Waiting On The Page Location)
當執行像 $browser->assertPathIs('/home') 這樣的路徑斷言時,如果 window.location.pathname 被異步更新,該斷言可能會失敗。您可以使用 waitForLocation 方法等待頁面位置變為指定的值:
$browser->waitForLocation('/secret');
The waitForLocation method can also be used to wait for the current window location to be a fully qualified URL:
$browser->waitForLocation('https://example.com/path');
您也可以等待某個 命名路由 的位置:
$browser->waitForRoute($routeName, $parameters);
等待頁面重新載入 (Waiting For Page Reloads)
如果您需要在執行某個操作後等待頁面重新載入,請使用 waitForReload 方法:
use Laravel\Dusk\Browser;
$browser->waitForReload(function (Browser $browser) {
$browser->press('Submit');
})
->assertSee('Success!');
由於通常在點擊按鈕後需要等待頁面重新載入,您可以使用 clickAndWaitForReload 方法以便捷完成此操作:
$browser->clickAndWaitForReload('.selector')
->assertSee('something');
等待 JavaScript 表達式 (Waiting On Javascript Expressions)
有時您可能希望暫停測試執行,直到某個 JavaScript 表達式評估為 true。您可以使用 waitUntil 方法輕鬆實現。傳遞表達式給該方法時,無需包含 return 關鍵字或結尾分號:
// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0');
// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);
等待 Vue 表達式 (Waiting On Vue Expressions)
waitUntilVue 與 waitUntilVueIsNot 方法可用於等待 Vue 元件 的某個屬性達到指定值:
// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Taylor', '@user');
// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');
等待 JavaScript 事件 (Waiting For Javascript Events)
The waitForEvent method can be used to pause the execution of a test until a JavaScript event occurs:
$browser->waitForEvent('load');
事件監聽器會附加到當前作用域,預設情況下為 body 元素。使用範圍選擇器時,事件監聽器會附加到匹配的元素上:
$browser->with('iframe', function (Browser $iframe) {
// Wait for the iframe's load event...
$iframe->waitForEvent('load');
});
您也可以將選擇器作為第二個參數傳遞給 waitForEvent 方法,以將事件監聽器附加到指定元素上:
$browser->waitForEvent('load', '.selector');
您也可以等待 document 與 window 物件上的事件:
// Wait until the document is scrolled...
$browser->waitForEvent('scroll', 'document');
// Wait a maximum of five seconds until the window is resized...
$browser->waitForEvent('resize', 'window', 5);
使用回呼等待 (Waiting With A Callback)
Dusk 中許多「等待」方法都依賴於底層的 waitUsing 方法。您可以直接使用該方法等待某個閉包返回 true。waitUsing 方法接受最大等待秒數、應評估閉包的間隔時間、閉包本身,以及可選的失敗訊息:
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");
將元素捲到可視區 (Scrolling An Element Into View)
有時您無法點擊元素,因為它位於瀏覽器可視區域之外。scrollIntoView 方法會捲動瀏覽器視窗,直到指定選擇器的元素進入可視範圍:
$browser->scrollIntoView('.selector')
->click('.selector');
可用的斷言 (Available Assertions)
Dusk 提供了多種可以對應用程式進行的斷言。所有可用的斷言已在下方列表中記載:
- assertTitle
- assertTitleContains
- assertUrlIs
- assertSchemeIs
- assertSchemeIsNot
- assertHostIs
- assertHostIsNot
- assertPortIs
- assertPortIsNot
- assertPathBeginsWith
- assertPathEndsWith
- assertPathContains
- assertPathIs
- assertPathIsNot
- assertRouteIs
- assertQueryStringHas
- assertQueryStringMissing
- assertFragmentIs
- assertFragmentBeginsWith
- assertFragmentIsNot
- assertHasCookie
- assertHasPlainCookie
- assertCookieMissing
- assertPlainCookieMissing
- assertCookieValue
- assertPlainCookieValue
- assertSee
- assertDontSee
- assertSeeIn
- assertDontSeeIn
- assertSeeAnythingIn
- assertSeeNothingIn
- assertCount
- assertScript
- assertSourceHas
- assertSourceMissing
- assertSeeLink
- assertDontSeeLink
- assertInputValue
- assertInputValueIsNot
- assertChecked
- assertNotChecked
- assertIndeterminate
- assertRadioSelected
- assertRadioNotSelected
- assertSelected
- assertNotSelected
- assertSelectHasOptions
- assertSelectMissingOptions
- assertSelectHasOption
- assertSelectMissingOption
- assertValue
- assertValueIsNot
- assertAttribute
- assertAttributeMissing
- assertAttributeContains
- assertAttributeDoesntContain
- assertAriaAttribute
- assertDataAttribute
- assertVisible
- assertPresent
- assertNotPresent
- assertMissing
- assertInputPresent
- assertInputMissing
- assertDialogOpened
- assertEnabled
- assertDisabled
- assertButtonEnabled
- assertButtonDisabled
- assertFocused
- assertNotFocused
- assertAuthenticated
- assertGuest
- assertAuthenticatedAs
- assertVue
- assertVueIsNot
- assertVueContains
- assertVueDoesntContain
assertTitle
斷言頁面標題與給定文字相符:
$browser->assertTitle($title);
assertTitleContains
斷言頁面標題包含給定文字:
$browser->assertTitleContains($title);
assertUrlIs
斷言當前 URL(不含查詢字串)與給定字串相符:
$browser->assertUrlIs($url);
assertSchemeIs
斷言當前 URL 的協議(scheme)與給定的協議相符:
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
斷言當前 URL 的協議(scheme)不匹配給定的協議:
$browser->assertSchemeIsNot($scheme);
assertHostIs
斷言當前 URL 的主機(host)與給定主機相符:
$browser->assertHostIs($host);
assertHostIsNot
斷言當前 URL 的主機(host)不匹配給定主機:
$browser->assertHostIsNot($host);
assertPortIs
斷言當前 URL 的埠(port)與給定埠相符:
$browser->assertPortIs($port);
assertPortIsNot
斷言當前 URL 的埠(port)不匹配給定埠:
$browser->assertPortIsNot($port);
assertPathBeginsWith
斷言當前 URL 的路徑以給定路徑開頭:
$browser->assertPathBeginsWith('/home');
assertPathEndsWith
斷言當前 URL 的路徑以給定路徑結尾:
$browser->assertPathEndsWith('/home');
assertPathContains
斷言當前 URL 的路徑包含給定路徑:
$browser->assertPathContains('/home');
assertPathIs
斷言當前路徑與給定路徑相符:
$browser->assertPathIs('/home');
assertPathIsNot
斷言當前路徑不匹配給定路徑:
$browser->assertPathIsNot('/home');
assertRouteIs
斷言當前 URL 與給定的命名路由(named route)的 URL 相符:
$browser->assertRouteIs($name, $parameters);
assertQueryStringHas
斷言給定的查詢字串參數存在:
$browser->assertQueryStringHas($name);
斷言給定的查詢字串參數存在且具有指定值:
$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
斷言給定的查詢字串參數不存在:
$browser->assertQueryStringMissing($name);
assertFragmentIs
斷言 URL 當前的 hash 片段與給定片段相符:
$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
斷言 URL 當前的 hash 片段以給定片段開頭:
$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
斷言 URL 當前的 hash 片段不匹配給定片段:
$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);
assertSeeAnythingIn
斷言指定選擇器內存在任意文字:
$browser->assertSeeAnythingIn($selector);
assertSeeNothingIn
斷言指定選擇器內沒有文字:
$browser->assertSeeNothingIn($selector);
assertCount
斷言匹配指定選擇器的元素出現指定次數:
$browser->assertCount($selector, $count);
assertScript
斷言指定的 JavaScript 表達式計算後等於給定值:
$browser->assertScript('window.isLoaded')
->assertScript('document.readyState', 'complete');
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
斷言指定的 checkbox 已被勾選:
$browser->assertChecked($field);
assertNotChecked
斷言指定的 checkbox 未被勾選:
$browser->assertNotChecked($field);
assertIndeterminate
斷言指定的 checkbox 處於不確定(indeterminate)狀態:
$browser->assertIndeterminate($field);
assertRadioSelected
斷言指定的 radio 欄位被選取:
$browser->assertRadioSelected($field, $value);
assertRadioNotSelected
斷言指定的 radio 欄位未被選取:
$browser->assertRadioNotSelected($field, $value);
assertSelected
斷言指定的下拉選單已選擇給定的值:
$browser->assertSelected($field, $value);
assertNotSelected
斷言指定的下拉選單未選擇給定的值:
$browser->assertNotSelected($field, $value);
assertSelectHasOptions
斷言給定的一組值可供選擇:
$browser->assertSelectHasOptions($field, $values);
assertSelectMissingOptions
斷言給定的一組值不可供選擇:
$browser->assertSelectMissingOptions($field, $values);
assertSelectHasOption
斷言給定的值可在指定欄位上被選取:
$browser->assertSelectHasOption($field, $value);
assertSelectMissingOption
斷言給定的值不可被選取:
$browser->assertSelectMissingOption($field, $value);
assertValue
斷言匹配指定選擇器的元素具有給定值:
$browser->assertValue($selector, $value);
assertValueIsNot
斷言匹配指定選擇器的元素不具有給定值:
$browser->assertValueIsNot($selector, $value);
assertAttribute
斷言匹配指定選擇器的元素在指定屬性中具有給定值:
$browser->assertAttribute($selector, $attribute, $value);
assertAttributeMissing
斷言匹配指定選擇器的元素缺少指定屬性:
$browser->assertAttributeMissing($selector, $attribute);
assertAttributeContains
斷言匹配指定選擇器的元素在指定屬性中包含給定值:
$browser->assertAttributeContains($selector, $attribute, $value);
assertAttributeDoesntContain
斷言匹配指定選擇器的元素在指定屬性中不包含給定值:
$browser->assertAttributeDoesntContain($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(或 data-content)屬性進行斷言:
$browser->assertDataAttribute('#row-1', 'content', 'attendees')
assertVisible
斷言匹配指定選擇器的元素可見:
$browser->assertVisible($selector);
assertPresent
斷言匹配指定選擇器的元素在原始碼中存在:
$browser->assertPresent($selector);
assertNotPresent
斷言匹配指定選擇器的元素在原始碼中不存在:
$browser->assertNotPresent($selector);
assertMissing
斷言匹配指定選擇器的元素不可見:
$browser->assertMissing($selector);
assertInputPresent
斷言具有指定名稱的輸入欄位存在:
$browser->assertInputPresent($name);
assertInputMissing
斷言具有指定名稱的輸入欄位在原始碼中不存在:
$browser->assertInputMissing($name);
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
Dusk 甚至允許您對 Vue 組件 的資料狀態進行斷言。例如,假設您的應用程式包含以下 Vue 組件:
<!-- HTML... -->
<profile dusk="profile-component"></profile>
// Component Definition...
Vue.component("profile", {
template: "<div>{{ user.name }}</div>",
data: function () {
return {
user: {
name: "Taylor",
},
};
},
});
您可以像下面這樣對 Vue 組件的狀態進行斷言:
test('vue', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
});
/**
* A basic Vue test example.
*/
public function test_vue(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', 'Taylor', '@profile-component');
});
}
assertVueIsNot
斷言給定的 Vue 組件資料屬性不等於指定值:
$browser->assertVueIsNot($property, $value, $componentSelector = null);
assertVueContains
斷言給定的 Vue 組件資料屬性為陣列且包含指定值:
$browser->assertVueContains($property, $value, $componentSelector = null);
assertVueDoesntContain
斷言給定的 Vue 組件資料屬性為陣列且不包含指定值:
$browser->assertVueDoesntContain($property, $value, $componentSelector = null);
頁面 (Pages)
有時候,測試需要按順序執行多個複雜操作,這可能讓您的測試變得較難閱讀和理解。Dusk Pages 允許您定義可表述的操作,之後可透過單一方法在特定頁面上執行。Pages 也允許您為應用或單一頁面定義常用選擇器的捷徑。
生成頁面 (Generating Pages)
要生成頁面物件,請執行 dusk:page Artisan 命令。所有頁面物件會放置在應用程式的 tests/Browser/Pages 目錄中:
php artisan dusk:page Login
配置頁面 (Configuring Pages)
預設情況下,頁面包含三個方法:url、assert 和 elements。現在我們會說明 url 與 assert 方法。elements 方法將在後文的 簡寫選擇器 部分詳細討論。
url 方法 (The Url Method)
url 方法應回傳代表該頁面的 URL 路徑。Dusk 在瀏覽器中導航到頁面時會使用該 URL:
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/login';
}
assert 方法 (The Assert Method)
assert 方法可以執行任何必要的斷言以驗證瀏覽器是否位於指定頁面。實際上,此方法內不必放入任何內容;不過若您願意,仍可在此執行斷言。當導航到該頁面時,這些斷言會自動執行:
/**
* 斷言瀏覽器位於該頁面。
*/
public function assert(Browser $browser): void
{
$browser->assertPathIs($this->url());
}
導航到頁面 (Navigating To Pages)
定義頁面後,您可以使用 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');
簡寫選擇器 (Shorthand Selectors)
頁面類中的 elements 方法允許您為頁面上的任意 CSS 選擇器定義簡潔且易記的快捷鍵。例如,我們可以為應用程式登入頁面的 "email" 輸入欄位定義一個快捷鍵:
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@email' => 'input[name=email]',
];
}
定義快捷選擇器後,您可以在任何通常使用完整 CSS 選擇器的地方使用該簡寫選擇器:
$browser->type('@email', 'taylor@laravel.com');
全局簡寫選擇器 (Global Shorthand Selectors)
安裝 Dusk 後,會在 tests/Browser/Pages 目錄中生成一個基礎的 Page 類。該類包含一個 siteElements 方法,可用於定義在整個應用程式中每個頁面都可用的全局簡寫選擇器:
/**
* Get the global element shortcuts for the site.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}
頁面方法 (Page Methods)
除了頁面上定義的預設方法外,您還可以定義其他方法以在測試中重複使用。例如,假設我們正在構建一個音樂管理應用,某頁面常見的操作可能是建立播放清單。與其在每個測試中重寫建立播放清單的邏輯,您可以在頁面類上定義 createPlaylist 方法:
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;
class Dashboard extends Page
{
// Other page methods...
/**
* Create a new playlist.
*/
public function createPlaylist(Browser $browser, string $name): void
{
$browser->type('name', $name)
->check('share')
->press('Create Playlist');
}
}
方法定義後,您可以在任何使用該頁面的測試中使用它。瀏覽器實例會自動作為第一個參數傳入自定義的頁面方法:
use Tests\Browser\Pages\Dashboard;
$browser->visit(new Dashboard)
->createPlaylist('My Playlist')
->assertSee('My Playlist');
元件 (Components)
元件(Components)類似於 Dusk 的「頁面物件(page objects)」,但它們更適用於在應用程式中反覆使用的 UI 或功能片段,例如導覽列或通知視窗。因此,元件並不綁定於特定的 URL。
生成元件 (Generating Components)
要生成元件,請執行 dusk:component Artisan 命令。新的元件會放在 tests/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
{
/**
* Get the root selector for the component.
*/
public function selector(): string
{
return '.date-picker';
}
/**
* 斷言瀏覽器頁面包含該組件。
*/
public function assert(Browser $browser): void
{
$browser->assertVisible($this->selector());
}
/**
* Get the element shortcuts for the component.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@date-field' => 'input.datepicker-input',
'@year-list' => 'div > div.datepicker-years',
'@month-list' => 'div > div.datepicker-months',
'@day-list' => 'div > div.datepicker-days',
];
}
/**
* Select the given date.
*/
public function selectDate(Browser $browser, int $year, int $month, int $day): void
{
$browser->click('@date-field')
->within('@year-list', function (Browser $browser) use ($year) {
$browser->click($year);
})
->within('@month-list', function (Browser $browser) use ($month) {
$browser->click($month);
})
->within('@day-list', function (Browser $browser) use ($day) {
$browser->click($day);
});
}
}
使用元件 (Using Components)
元件定義完成後,我們可以在任何測試中輕鬆地在日期選擇器中選擇日期。如果選擇日期所需的邏輯改變,我們只需更新該元件即可:
<?php
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
pest()->use(DatabaseMigrations::class);
test('basic example', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function (Browser $browser) {
$browser->selectDate(2019, 1, 30);
})
->assertSee('January');
});
});
<?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
{
/**
* A basic component test example.
*/
public function test_basic_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->within(new DatePicker, function (Browser $browser) {
$browser->selectDate(2019, 1, 30);
})
->assertSee('January');
});
}
}
component 方法可用於取得限定於指定元件範圍的瀏覽器實例:
$datePicker = $browser->component(new DatePickerComponent);
$datePicker->selectDate(2019, 1, 30);
$datePicker->assertSee('January');
持續整合 (Continuous Integration)
[!WARNING] 大多數 Dusk 的持續整合(CI)配置預期您的 Laravel 應用會使用內建的 PHP 開發伺服器在 8000 端口提供服務。因此在繼續之前,請確認您的持續整合環境中
APP_URL環境變數設為http://127.0.0.1:8000。
Heroku CI
要在 Heroku CI 上執行 Dusk 測試,請在 Heroku 的 app.json 文件中添加以下 Google Chrome buildpack 與腳本:
{
"environments": {
"test": {
"buildpacks": [
{ "url": "heroku/php" },
{
"url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing"
}
],
"scripts": {
"test-setup": "cp .env.testing .env",
"test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /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:
- 8.2
addons:
chrome: stable
install:
- cp .env.testing .env
- travis_retry composer install --no-interaction --prefer-dist
- 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 --no-reload &
script:
- php artisan dusk
GitHub Actions
如果您使用 GitHub Actions 來運行 Dusk 測試,可以使用下列配置作為起點。與 Travis CI 類似,我們將使用 php artisan serve 命令啟動 PHP 內建的 Web 伺服器:
name: CI
on: [push]
jobs:
dusk-php:
runs-on: ubuntu-latest
env:
APP_URL: "http://127.0.0.1:8000"
DB_USERNAME: root
DB_PASSWORD: root
MAIL_MAILER: log
steps:
- uses: actions/checkout@v5
- 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 --prefer-dist --optimize-autoloader
- name: Generate Application Key
run: php artisan key:generate
- name: Upgrade Chrome Driver
run: php artisan dusk:chrome-driver --detect
- name: Start Chrome Driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
- name: Run Laravel Server
run: php artisan serve --no-reload &
- name: Run Dusk Tests
run: php artisan dusk
- name: Upload Screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: tests/Browser/screenshots
- name: Upload Console Logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: console
path: tests/Browser/console
Chipper CI
如果您使用 Chipper CI 來運行 Dusk 測試,可以使用下列配置作為起點。此處我們會使用 PHP 的內建伺服器來運行 Laravel,以便監聽請求:
# file .chipperci.yml
version: 1
environment:
php: 8.2
node: 16
# Include Chrome in the build environment
services:
- dusk
# Build all commits
on:
push:
branches: .*
pipeline:
- name: Setup
cmd: |
cp -v .env.example .env
composer install --no-interaction --prefer-dist --optimize-autoloader
php artisan key:generate
# Create a dusk env file, ensuring APP_URL uses BUILD_HOST
cp -v .env .env.dusk.ci
sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
- name: Compile Assets
cmd: |
npm ci --no-audit
npm run build
- name: Browser Tests
cmd: |
php -S [::0]:8000 -t public 2>server.log &
sleep 2
php artisan dusk:chrome-driver $CHROME_DRIVER
php artisan dusk --env=ci
如需瞭解更多在 Chipper CI 上運行 Dusk 測試(包括如何使用資料庫)的資訊,請參考 官方 Chipper CI 文檔。