Laravel Dusk
簡介
⚠️ 警告
Pest 4 現在包含了自動化瀏覽器測試,與 Laravel Dusk 相比,它提供了顯著的效能與可用性改進。對於新專案,我們建議使用 Pest 進行瀏覽器測試。
Laravel Dusk 提供了一個表達性強、易於使用的瀏覽器自動化與測試 API。預設情況下,Dusk 不需要你在本機電腦上安裝 JDK 或 Selenium。相反地,Dusk 使用一個獨立的 ChromeDriver 安裝。不過,你可以自由使用任何其他與 Selenium 相容的驅動程式。
安裝
首先,你應該安裝 Google Chrome 並在你的專案中新增 laravel/dusk
Composer 依賴套件:
composer require laravel/dusk --dev
⚠️ 警告
如果你正在手動註冊 Dusk 的服務提供者 (service provider),你絕不應該在你的正式環境中註冊它,因為這樣做可能會導致任意使用者能夠向你的應用程式進行認證。
安裝 Dusk 套件後,執行 dusk:install
Artisan 指令。dusk:install
指令會建立一個 tests/Browser
目錄、一個範例 Dusk 測試,並為你的作業系統安裝 Chrome Driver 二進位檔:
php artisan dusk:install
接著,在你的應用程式的 .env
檔案中設定 APP_URL
環境變數。此值應與你在瀏覽器中存取應用程式所使用的 URL 相符。
📌 備註
如果你正在使用 Laravel Sail 來管理你的本機開發環境,請同時查閱 Sail 文件中關於 配置與執行 Dusk 測試 的說明。
管理 ChromeDriver 安裝
如果你想安裝與 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
⚠️ 警告
Dusk 要求 chromedriver
二進位檔是可執行的。如果你在執行 Dusk 時遇到問題,應使用以下指令確保二進位檔是可執行的:chmod -R 0755 vendor/laravel/dusk/bin/
。
使用其他瀏覽器
預設情況下,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()
);
}
開始使用
產生測試
要產生一個 Dusk 測試,請使用 dusk:make
Artisan 指令。產生的測試將會放在 tests/Browser
目錄中:
php artisan dusk:make LoginTest
每次測試後重設資料庫
您編寫的大部分測試都會與從應用程式資料庫中擷取資料的頁面互動;然而,您的 Dusk 測試不應使用 RefreshDatabase
trait。RefreshDatabase
trait 利用資料庫交易,這將不適用或無法跨 HTTP 請求使用。相反地,您有兩種選擇:DatabaseMigrations
trait 和 DatabaseTruncation
trait。
使用 Database 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;
//
}
⚠️ 警告
在執行 Dusk 測試時,不能使用 SQLite 記憶體內資料庫。由於瀏覽器在其自己的程序中執行,它將無法存取其他程序的記憶體內資料庫。
使用 Database 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
屬性:
📌 備註
如果您正在使用 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
{
//
}
執行測試
要執行您的瀏覽器測試,請執行 dusk
Artisan 指令:
php artisan dusk
如果您上次執行 dusk
指令時發生測試失敗,您可以透過先使用 dusk:fails
指令重新執行失敗的測試來節省時間:
php artisan dusk:fails
dusk
指令接受任何通常由 Pest / PHPUnit 測試執行器接受的參數,例如允許您只為給定的 group 執行測試:
php artisan dusk --group=foo
📌 備註
如果您正在使用 Laravel Sail 管理您的本地開發環境,請參閱 Sail 文件中關於 配置和執行 Dusk 測試 的說明。
手動啟動 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()
);
}
環境處理
若要強制 Dusk 在執行測試時使用其自己的環境檔案,請在您的專案根目錄中建立一個 .env.dusk.{environment}
檔案。例如,如果您將從 local
環境中啟動 dusk
指令,您應該建立一個 .env.dusk.local
檔案。
執行測試時,Dusk 會備份您的 .env
檔案,並將您的 Dusk 環境重新命名為 .env
。一旦測試完成,您的 .env
檔案將會被還原。
瀏覽器基礎
建立瀏覽器
首先,我們來編寫一個測試,以驗證我們可以登入我們的應用程式。在產生測試之後,我們可以修改它以導覽到登入頁面,輸入一些憑證,然後點擊「Login」按鈕。若要建立瀏覽器實例,您可以從您的 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' => '[email protected]',
]);
$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' => '[email protected]',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('Login')
->assertPathIs('/home');
});
}
}
如您在上面的範例中看到的,browse
方法接受一個閉包。Dusk 將自動把瀏覽器實例傳遞給此閉包,它是用於與您的應用程式互動並進行斷言的主要物件。
建立多個瀏覽器
有時候您可能需要多個瀏覽器才能正確執行測試。例如,測試一個與 websockets 互動的聊天畫面可能需要多個瀏覽器。若要建立多個瀏覽器,只需在 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');
});
導覽
visit
方法可用於導覽到您的應用程式中指定的 URI:
$browser->visit('/login');
您可以使用 visitRoute
方法導覽到 具名路由:
$browser->visitRoute($routeName, $parameters);
您可以使用 back
和 forward
方法「返回」和「前進」:
$browser->back();
$browser->forward();
您可以使用 refresh
方法重新整理頁面:
$browser->refresh();
調整瀏覽器視窗大小
您可以使用 resize
方法調整瀏覽器視窗的大小:
$browser->resize(1920, 1080);
maximize
方法可用於最大化瀏覽器視窗:
$browser->maximize();
fitContent
方法將調整瀏覽器視窗大小以符合其內容大小:
$browser->fitContent();
當測試失敗時,Dusk 將在擷取螢幕截圖之前自動調整瀏覽器大小以符合內容。您可以透過在測試中呼叫 disableFitOnFailure
方法來停用此功能:
$browser->disableFitOnFailure();
您可以使用 move
方法將瀏覽器視窗移動到螢幕上的不同位置:
$browser->move($x = 100, $y = 100);
瀏覽器巨集
如果您想定義一個可以在各種測試中重複使用的自訂瀏覽器方法,您可以使用 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');
});
認證
通常,您會測試需要認證的頁面。您可以使用 Dusk 的 loginAs
方法來避免在每次測試期間與應用程式的登入畫面互動。loginAs
方法接受與您的可認證模型相關聯的主鍵或可認證模型實例:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});
⚠️ 警告
使用 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
您可以使用 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');
擷取螢幕截圖
您可以使用 screenshot
方法擷取螢幕截圖並以指定檔名儲存。所有螢幕截圖將儲存在 tests/Browser/screenshots
目錄中:
$browser->screenshot('filename');
responsiveScreenshots
方法可用於在各種斷點處擷取一系列螢幕截圖:
$browser->responsiveScreenshots('filename');
screenshotElement
方法可用於擷取頁面上特定元素的螢幕截圖:
$browser->screenshotElement('#selector', 'filename');
將主控台輸出儲存至磁碟
您可以使用 storeConsoleLog
方法將當前瀏覽器的主控台輸出以指定檔名寫入磁碟。主控台輸出將儲存在 tests/Browser/console
目錄中:
$browser->storeConsoleLog('filename');
將頁面原始碼儲存至磁碟
您可以使用 storeSource
方法將當前頁面的原始碼以指定檔名寫入磁碟。頁面原始碼將儲存在 tests/Browser/source
目錄中:
$browser->storeSource('filename');
與元素互動
Dusk 選擇器
替元素選擇好的 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');
文字、值與屬性
擷取與設定值
Dusk 提供了多種方法來與頁面元素的當前值、顯示文字和屬性進行互動。例如,要取得符合給定 CSS 或 Dusk 選擇器的元素的「值」,請使用 value
方法:
// Retrieve the value...
$value = $browser->value('selector');
// Set the value...
$browser->value('selector', 'value');
您可以使用 inputValue
方法來取得具有給定欄位名稱的輸入元素的「值」:
$value = $browser->inputValue('field');
擷取文字
text
方法可用於擷取符合給定選擇器之元素的顯示文字:
$text = $browser->text('selector');
擷取屬性
最後,attribute
方法可用於擷取符合給定選擇器之元素的屬性值:
$attribute = $browser->attribute('selector', 'value');
與表單互動
輸入值
Dusk 提供了多種方法來與表單和輸入元素互動。首先,讓我們看看將文字輸入到輸入欄位中的範例:
$browser->type('email', '[email protected]');
請注意,儘管該方法在必要時接受一個參數,但我們不需要將 CSS 選擇器傳遞給 type
方法。如果未提供 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');
下拉式選單
要選取 select
元素上的可用值,您可以使用 select
方法。與 type
方法一樣,select
方法不需要完整的 CSS 選擇器。當將值傳遞給 select
方法時,您應該傳遞底層的選項值而不是顯示文字:
$browser->select('size', 'Large');
您可以省略第二個參數來選取隨機選項:
$browser->select('size');
透過將陣列作為 select
方法的第二個參數,您可以指示該方法選取多個選項:
$browser->select('categories', ['Art', 'Music']);
核取方塊
要「核取」一個核取方塊輸入,您可以使用 check
方法。與許多其他輸入相關方法一樣,不需要完整的 CSS 選擇器。如果找不到符合的 CSS 選擇器,Dusk 將會搜尋具有匹配 name
屬性的核取方塊。
$browser->check('terms');
uncheck
方法可用於「取消核取」一個核取方塊輸入:
$browser->uncheck('terms');
單選按鈕
要「選取」一個 radio
輸入選項,您可以使用 radio
方法。與許多其他輸入相關方法一樣,不需要完整的 CSS 選擇器。如果找不到符合的 CSS 選擇器,Dusk 將會搜尋具有匹配 name
和 value
屬性的 radio
輸入。
$browser->radio('size', 'large');
附加檔案
attach
方法可用於將檔案附加到 file
輸入元素。與許多其他輸入相關方法一樣,不需要完整的 CSS 選擇器。如果找不到符合的 CSS 選擇器,Dusk 將會搜尋具有匹配 name
屬性的 file
輸入。
$browser->attach('photo', __DIR__.'/photos/mountains.png');
⚠️ 警告
附加功能要求您的伺服器安裝並啟用 Zip
PHP 擴充功能。
按下按鈕
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);
點擊連結
要點擊連結,您可以在瀏覽器實例上使用 clickLink
方法。clickLink
方法將點擊具有給定顯示文字的連結:
$browser->clickLink($linkText);
您可以使用 seeLink
方法來判斷具有給定顯示文字的連結是否在頁面上可見:
if ($browser->seeLink($linkText)) {
// ...
}
⚠️ 警告
這些方法與 jQuery 互動。如果頁面上沒有 jQuery,Dusk 將會自動將其注入頁面,以便在測試期間可用。
使用鍵盤
keys
方法允許您向給定元素提供比 type
方法更複雜的輸入序列。例如,您可以指示 Dusk 在輸入值時按住修飾鍵。在這個範例中,當 taylor
輸入到與給定選擇器匹配的元素時,將按住 shift
鍵。在 taylor
輸入後,將不帶任何修飾鍵輸入 swift
:
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
keys
方法的另一個有用的用例是向應用程式的主要 CSS 選擇器發送「鍵盤快速鍵」組合:
$browser->keys('.app', ['{command}', 'j']);
📌 備註
所有修飾鍵,例如 {command}
,都用 {}
字符包圍,並且與 Facebook\WebDriver\WebDriverKeys
類中定義的常數匹配,這些常數可以在 GitHub 上找到。
流暢的鍵盤互動
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
類提供的 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());
使用滑鼠
點擊元素
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');
滑鼠懸停
當您需要將滑鼠懸停在與給定 CSS 或 Dusk 選擇器匹配的元素上時,可以使用 mouseover
方法:
$browser->mouseover('.selector');
拖放
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 對話方塊
Dusk 提供各種方法來與 JavaScript 對話方塊互動。例如,您可以使用 waitForDialog
方法來等待 JavaScript 對話方塊出現。此方法接受一個可選參數,指示等待對話方塊出現的秒數:
$browser->waitForDialog($seconds = null);
assertDialogOpened
方法可用於斷言對話方塊已顯示並包含給定訊息:
$browser->assertDialogOpened('Dialog message');
如果 JavaScript 對話方塊包含提示,您可以使用 typeInDialog
方法在提示中輸入值:
$browser->typeInDialog('Hello World');
要透過點擊「OK」按鈕關閉開啟的 JavaScript 對話方塊,您可以調用 acceptDialog
方法:
$browser->acceptDialog();
要透過點擊「Cancel」按鈕關閉開啟的 JavaScript 對話方塊,您可以調用 dismissDialog
方法:
$browser->dismissDialog();
與內嵌框架互動
如果您需要與 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');
});
限定選擇器範圍
有時您可能希望在給定選擇器範圍內執行多個操作。例如,您可能希望斷言某些文字僅存在於表格中,然後點擊該表格中的按鈕。您可以使用 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');
});
});
等待元素
當測試大量使用 JavaScript 的應用程式時,在繼續進行測試之前,通常有必要「等待」某些元素或資料可用。Dusk 讓這一切變得輕而易舉。透過多種方法,您可以等待元素在頁面上變得可見,甚至可以等待直到給定的 JavaScript 表達式評估為 true
。
等待
如果您只需要將測試暫停給定的毫秒數,請使用 pause
方法:
$browser->pause(1000);
如果您只需要在給定條件為 true
時暫停測試,請使用 pauseIf
方法:
$browser->pauseIf(App::environment('production'), 1000);
同樣地,除非給定條件為 true
,否則您可能需要暫停測試,您可以這麼做,請使用 pauseUnless
方法:
$browser->pauseUnless(App::environment('testing'), 1000);
等待選擇器
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);
條件式限定選擇器範圍
有時,您可能希望等待與給定選擇器相符的元素出現,然後與該元素互動。例如,您可能希望等待直到對話框視窗可用,然後在對話框中按下「OK」按鈕。whenAvailable
方法可用來達成此目的。在給定閉包中執行的所有元素操作都將限定於原始選擇器:
$browser->whenAvailable('.modal', function (Browser $modal) {
$modal->assertSee('Hello World')
->press('OK');
});
等待文字
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);
等待連結
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);
等待輸入
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);
等待頁面位置
當進行像 $browser->assertPathIs('/home')
這樣的路徑斷言時,如果 window.location.pathname
正在非同步更新,斷言可能會失敗。您可以使用 waitForLocation
方法來等待位置成為給定值:
$browser->waitForLocation('/secret');
waitForLocation
方法也可用來等待目前視窗位置成為完整的 URL:
$browser->waitForLocation('https://example.com/path');
您也可以等待 具名路由 的位置:
$browser->waitForRoute($routeName, $parameters);
等待頁面重新載入
如果您需要在執行動作後等待頁面重新載入,請使用 waitForReload
方法:
use Laravel\Dusk\Browser;
$browser->waitForReload(function (Browser $browser) {
$browser->press('Submit');
})
->assertSee('Success!');
由於等待頁面重新載入的需求通常發生在點擊按鈕之後,為了方便起見,您可以使用 clickAndWaitForReload
方法:
$browser->clickAndWaitForReload('.selector')
->assertSee('something');
等待 JavaScript 表達式
有時您可能希望暫停測試的執行,直到給定的 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 表達式
waitUntilVue
和 waitUntilVueIsNot
方法可用來等待直到 Vue component 屬性具有給定值:
// 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 事件
waitForEvent
方法可用來暫停測試執行,直到發生 JavaScript 事件為止:
$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);
使用回呼等待
Dusk 中的許多「等待」方法都依賴於底層的 waitUsing
方法。您可以直接使用此方法來等待給定的閉包回傳 true
。waitUsing
方法接受最大等待秒數、評估閉包的間隔、閉包,以及一個可選的失敗訊息:
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");
將元素捲動至視野中
有時您可能無法點擊某個元素,因為它位於瀏覽器的可視區域之外。scrollIntoView
方法將會捲動瀏覽器視窗,直到指定選擇器的元素進入視野中:
$browser->scrollIntoView('.selector')
->click('.selector');
可用的斷言
Dusk 提供了多種可對您的應用程式進行的斷言。所有可用的斷言都列於下方清單中:
assertTitleassertTitleContainsassertUrlIsassertSchemeIsassertSchemeIsNotassertHostIsassertHostIsNotassertPortIsassertPortIsNotassertPathBeginsWithassertPathEndsWithassertPathContainsassertPathIsassertPathIsNotassertRouteIsassertQueryStringHasassertQueryStringMissingassertFragmentIsassertFragmentBeginsWithassertFragmentIsNotassertHasCookieassertHasPlainCookieassertCookieMissingassertPlainCookieMissingassertCookieValueassertPlainCookieValueassertSeeassertDontSeeassertSeeInassertDontSeeInassertSeeAnythingInassertSeeNothingInassertCountassertScriptassertSourceHasassertSourceMissingassertSeeLinkassertDontSeeLinkassertInputValueassertInputValueIsNotassertCheckedassertNotCheckedassertIndeterminateassertRadioSelectedassertRadioNotSelectedassertSelectedassertNotSelectedassertSelectHasOptionsassertSelectMissingOptionsassertSelectHasOptionassertSelectMissingOptionassertValueassertValueIsNotassertAttributeassertAttributeMissingassertAttributeContainsassertAttributeDoesntContainassertAriaAttributeassertDataAttributeassertVisibleassertPresentassertNotPresentassertMissingassertInputPresentassertInputMissingassertDialogOpenedassertEnabledassertDisabledassertButtonEnabledassertButtonDisabledassertFocusedassertNotFocusedassertAuthenticatedassertGuestassertAuthenticatedAsassertVueassertVueIsNotassertVueContainsassertVueDoesntContain
assertTitle
斷言頁面標題與給定文字相符:
$browser->assertTitle($title);
assertTitleContains
斷言頁面標題包含給定文字:
$browser->assertTitleContains($title);
assertUrlIs
斷言當前 URL(不包含查詢字串)與給定字串相符:
$browser->assertUrlIs($url);
assertSchemeIs
斷言當前 URL 協議與給定協議相符:
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
斷言當前 URL 協議與給定協議不符:
$browser->assertSchemeIsNot($scheme);
assertHostIs
斷言當前 URL 主機與給定主機相符:
$browser->assertHostIs($host);
assertHostIsNot
斷言當前 URL 主機與給定主機不符:
$browser->assertHostIsNot($host);
assertPortIs
斷言當前 URL 埠號與給定埠號相符:
$browser->assertPortIs($port);
assertPortIsNot
斷言當前 URL 埠號與給定埠號不符:
$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 與給定 具名路由 的 URL 相符:
$browser->assertRouteIs($name, $parameters);
assertQueryStringHas
斷言給定的查詢字串參數存在:
$browser->assertQueryStringHas($name);
斷言給定的查詢字串參數存在並具有給定值:
$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
斷言給定的查詢字串參數不存在:
$browser->assertQueryStringMissing($name);
assertFragmentIs
斷言 URL 的當前雜湊片段與給定片段相符:
$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
斷言 URL 的當前雜湊片段以給定片段開頭:
$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
斷言 URL 的當前雜湊片段與給定片段不符:
$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
斷言指定的核取方塊已選取:
$browser->assertChecked($field);
assertNotChecked
斷言指定的核取方塊未選取:
$browser->assertNotChecked($field);
assertIndeterminate
斷言指定的核取方塊處於不確定狀態:
$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
屬性:
$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 component 資料的狀態進行斷言。舉例來說,假設您的應用程式包含以下 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);
頁面
有時,測試需要依序執行數個複雜的動作。這可能會使您的測試更難閱讀和理解。Dusk 頁面讓您可以定義富有表達力的動作,然後可以透過單一方法在給定頁面上執行這些動作。頁面也允許您為應用程式或單一頁面定義常用選擇器的快捷方式。
產生頁面
若要產生頁面物件,請執行 dusk:page
Artisan 指令。所有頁面物件都將放置在應用程式的 tests/Browser/Pages
目錄中:
php artisan dusk:page Login
配置頁面
預設情況下,頁面有三個方法:url
、assert
和 elements
。我們現在將討論 url
和 assert
方法。elements
方法將在 下方詳述。
url
方法
url
方法應傳回代表該頁面的 URL 路徑。Dusk 將在瀏覽器中導覽至該頁面時使用此 URL:
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/login';
}
assert
方法
assert
方法可以執行任何必要的斷言,以驗證瀏覽器確實位於給定頁面上。實際上不必在此方法中放置任何內容;但是,如果您願意,可以自由地執行這些斷言。這些斷言將在導覽至頁面時自動執行:
/**
* Assert that the browser is on the page.
*/
public function assert(Browser $browser): void
{
$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」輸入欄位定義一個快捷方式:
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@email' => 'input[name=email]',
];
}
定義快捷方式後,您可以在任何通常使用完整 CSS 選擇器的地方使用簡寫選擇器:
$browser->type('@email', '[email protected]');
全域簡寫選擇器
安裝 Dusk 後,一個基礎 Page
類別將會放置在您的 tests/Browser/Pages
目錄中。此類別包含一個 siteElements
方法,可用於定義應該在應用程式中所有頁面都可用的全域簡寫選擇器:
/**
* Get the global element shortcuts for the site.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}
頁面方法
除了頁面上定義的預設方法外,您還可以定義可在測試中使用的其他方法。例如,假設我們正在建置一個音樂管理應用程式。應用程式某個頁面的一個常見動作可能是建立一個播放清單。與其在每個測試中重複編寫建立播放清單的邏輯,您可以在頁面類別上定義一個 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');
元件
元件與 Dusk 的「頁面物件」類似,但它專用於應用程式中重複使用的 UI 片段和功能,例如導覽列或通知視窗。因此,元件不綁定到特定的 URL。
產生元件
若要產生元件,請執行 dusk:component
Artisan 命令。新的元件會放置在 tests/Browser/Components
目錄中:
php artisan dusk:component DatePicker
如上所示,「日期選擇器」就是一個可能存在於應用程式中多個頁面上的元件範例。若要在整個測試套件中的數十個測試中手動編寫瀏覽器自動化邏輯來選擇日期會很麻煩。因此,我們可以定義一個 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';
}
/**
* Assert that the browser page contains the component.
*/
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);
});
}
}
使用元件
一旦元件被定義,我們就可以在任何測試中輕鬆地從日期選擇器中選擇日期。而且,如果選擇日期所需的邏輯發生變化,我們只需要更新元件即可:
<?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');
持續整合
⚠️ 警告
大多數 Dusk 持續整合配置預期您的 Laravel 應用程式會透過內建的 PHP 開發伺服器在 8000 埠提供服務。因此,在繼續之前,您應該確保您的持續整合環境具有 APP_URL
環境變數值 http://127.0.0.1:8000
。
Heroku CI
要在 Heroku CI 上執行 Dusk 測試,請將以下 Google Chrome buildpack 和指令碼新增至您的 Heroku app.json
檔案中:
{
"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 的內建網頁伺服器:
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 測試,您可以使用以下配置檔案作為起點。與 TravisCI 類似,我們將使用 php artisan serve
指令來啟動 PHP 的內建網頁伺服器:
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 官方文件。