Laravel Dusk
簡介
Laravel Dusk 提供了一個豐富、簡單易用的瀏覽器自動化與測試 API。預設情況下,使用 Dusk 不需要額外在本機電腦上安裝 JDK 或 Slenium。Dusk 會使用獨立的 ChromeDriver 安裝。不過,也可以自由使用其他 Selenium 相容的驅動器。
安裝
要開始使用 Dusk,請先安裝 Google Chrome,並將 laravel/dusk
Composer 相依性套件加到專案中:
1composer require laravel/dusk --dev
1composer require laravel/dusk --dev
若要手動註冊 Dusk 的 Service Provider,請不要在正式環境內加上該 Provider,因為這麼會讓所有人都能任意登入任何使用者。
安裝好 Dusk 套件後,請執行 dusk:install
Artisan 指令。dusk:install
指令會建立 tests/Browser
目錄、一個 Dusk 範例測試、並安裝適用於你的作業系統的 Chrome Driver 二進位執行檔:
1php artisan dusk:install
1php artisan dusk:install
接著,請在專案的 .env
檔內設定 APP_URL
環境變數。該變數應符合要在瀏覽器內存取專案的 URL。
若使用 Laravel Sail 來管理本機開發環境,也請一併參考 Sail 說明文件中有關設定與執行 Dusk 測試的部分。
管理 ChromeDriver 安裝
若要安裝與 dusk:install
指令所安裝不同的 ChromeDriver 版本,可使用 dusk:chrome-driver
指令:
1# Install the latest version of ChromeDriver for your OS...2php artisan dusk:chrome-driver34# Install a given version of ChromeDriver for your OS...5php artisan dusk:chrome-driver 8667# Install a given version of ChromeDriver for all supported OSs...8php artisan dusk:chrome-driver --all910# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...11php artisan dusk:chrome-driver --detect
1# Install the latest version of ChromeDriver for your OS...2php artisan dusk:chrome-driver34# Install a given version of ChromeDriver for your OS...5php artisan dusk:chrome-driver 8667# Install a given version of ChromeDriver for all supported OSs...8php artisan dusk:chrome-driver --all910# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...11php 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:
1/**2 * Prepare for Dusk test execution.3 *4 * @beforeClass5 */6public static function prepare(): void7{8 // static::startChromeDriver();9}
1/**2 * Prepare for Dusk test execution.3 *4 * @beforeClass5 */6public static function prepare(): void7{8 // static::startChromeDriver();9}
接著,可以修改 driver
方法來連先到所選的 URL 與連結埠。另外,也可以修改應傳給 WebDriver 的「Desired Capabilities (所需功能)」:
1use Facebook\WebDriver\Remote\RemoteWebDriver;23/**4 * Create the RemoteWebDriver instance.5 */6protected function driver(): RemoteWebDriver7{8 return RemoteWebDriver::create(9 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()10 );11}
1use Facebook\WebDriver\Remote\RemoteWebDriver;23/**4 * Create the RemoteWebDriver instance.5 */6protected function driver(): RemoteWebDriver7{8 return RemoteWebDriver::create(9 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()10 );11}
入門
產生測試
若要產生 Dusk 測試,請使用 dusk:make
Artisan 指令。產生的測試將放置於 tests/Browser
目錄內:
1php artisan dusk:make LoginTest
1php artisan dusk:make LoginTest
Resetting the Database After Each Test
我們要寫的測試大部分都會使用到一些會從資料庫中取得資料的頁面。不過,Dusk 測試不應該使用 RefreshDatabase
Trait。RefreshDatabase
Trait 使用的是資料庫 Transaction,而在多個 HTTP 間是沒辦法使用 Trasaction 的。因此,有兩個替代方案:DatabaseMigrations
Trait 與 DatabaseTruncation
Trait。
使用資料庫 Migration
DatabaseMigrations
Trait 會在每個測試前執行資料庫 Migration。不過,在各個測試前 Drop 資料表再重建一次通常會比 Trauncate 資料表來得慢:
1<?php23use Illuminate\Foundation\Testing\DatabaseMigrations;4use Laravel\Dusk\Browser;56uses(DatabaseMigrations::class);78//
1<?php23use Illuminate\Foundation\Testing\DatabaseMigrations;4use Laravel\Dusk\Browser;56uses(DatabaseMigrations::class);78//
1<?php23namespace Tests\Browser;45use Illuminate\Foundation\Testing\DatabaseMigrations;6use Laravel\Dusk\Browser;7use Tests\DuskTestCase;89class ExampleTest extends DuskTestCase10{11 use DatabaseMigrations;1213 //14}
1<?php23namespace Tests\Browser;45use Illuminate\Foundation\Testing\DatabaseMigrations;6use Laravel\Dusk\Browser;7use Tests\DuskTestCase;89class ExampleTest extends DuskTestCase10{11 use DatabaseMigrations;1213 //14}
在記憶體內的 SQLite 資料庫無法在執行 Dusk 測試時使用。由於瀏覽器會在自己的處理程序內執行,因此將無法存取其他處理程序中在記憶體內的資料庫。
使用資料庫 Truncation
DatabaseTruncation
Trait 會在第一個測試前執行資料庫 Migration,以確保資料庫資料表有被正確建立。接著,在之後的測試中,資料庫的資料表只會被 Truncate,這樣一來比起重新執行所有 Migration 來說會快很多:
1<?php23use Illuminate\Foundation\Testing\DatabaseTruncation;4use Laravel\Dusk\Browser;56uses(DatabaseTruncation::class);78//
1<?php23use Illuminate\Foundation\Testing\DatabaseTruncation;4use Laravel\Dusk\Browser;56uses(DatabaseTruncation::class);78//
1<?php23namespace Tests\Browser;45use App\Models\User;6use Illuminate\Foundation\Testing\DatabaseTruncation;7use Laravel\Dusk\Browser;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 use DatabaseTruncation;1314 //15}
1<?php23namespace Tests\Browser;45use App\Models\User;6use Illuminate\Foundation\Testing\DatabaseTruncation;7use Laravel\Dusk\Browser;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 use DatabaseTruncation;1314 //15}
預設情況下,這個 Trait 會 Truncate 除了 migrations
資料表以外的所有資料表。若要自定要 Truncate 的資料表,可以在測試類別上定義 $tablesToTruncate
屬性:
If you are using Pest, you should define properties or methods on the base DuskTestCase
class or on any class your test file extends.
1/**2 * Indicates which tables should be truncated.3 *4 * @var array5 */6protected $tablesToTruncate = ['users'];
1/**2 * Indicates which tables should be truncated.3 *4 * @var array5 */6protected $tablesToTruncate = ['users'];
或者,也可以在測試類別上定義 $exceptTables
來指定在 Truncate 時要排除哪些資料表:
1/**2 * Indicates which tables should be excluded from truncation.3 *4 * @var array5 */6protected $exceptTables = ['users'];
1/**2 * Indicates which tables should be excluded from truncation.3 *4 * @var array5 */6protected $exceptTables = ['users'];
若要指定要 Truncate 資料表的資料庫連線,可在測試類別上定義 $connectionsToTruncate
屬性:
1/**2 * Indicates which connections should have their tables truncated.3 *4 * @var array5 */6protected $connectionsToTruncate = ['mysql'];
1/**2 * Indicates which connections should have their tables truncated.3 *4 * @var array5 */6protected $connectionsToTruncate = ['mysql'];
若想在資料庫修剪 (Truncation) 進行前後執行程式碼,可在測試類別中定義 beforeTruncatingDatabase
或 afterTruncatingDatabase
方法:
1/**2 * Perform any work that should take place before the database has started truncating.3 */4protected function beforeTruncatingDatabase(): void5{6 //7}89/**10 * Perform any work that should take place after the database has finished truncating.11 */12protected function afterTruncatingDatabase(): void13{14 //15}
1/**2 * Perform any work that should take place before the database has started truncating.3 */4protected function beforeTruncatingDatabase(): void5{6 //7}89/**10 * Perform any work that should take place after the database has finished truncating.11 */12protected function afterTruncatingDatabase(): void13{14 //15}
執行測試
若要執行瀏覽器測試,請執行 dusk
Artisan 指令:
1php artisan dusk
1php artisan dusk
若在上次執行 dusk
指令時有測試失敗了,則可以通過 dusk:fails
指令來先重新執行失敗的測試以節省時間:
1php artisan dusk:fails
1php artisan dusk:fails
The dusk
command accepts any argument that is normally accepted by the Pest / PHPUnit test runner, such as allowing you to only run the tests for a given group:
1php artisan dusk --group=foo
1php artisan dusk --group=foo
若使用 Laravel Sail 來管理本機開發環境,請參考 Sail 說明文件中有關設定與執行 Dusk 測試的部分。
手動啟動 ChromeDriver
預設情況下,Dusk 會自動嘗試開啟 ChromeDriver。若你所使用的系統無法自動開啟 ChromeDriver,則可以在執行 dusk
指令前手動啟動 ChromeDriver。若想手動啟動 ChromeDriver,則應先在 test/DuskTestCase.php
檔中將下列部分註解掉:
1/**2 * Prepare for Dusk test execution.3 *4 * @beforeClass5 */6public static function prepare(): void7{8 // static::startChromeDriver();9}
1/**2 * Prepare for Dusk test execution.3 *4 * @beforeClass5 */6public static function prepare(): void7{8 // static::startChromeDriver();9}
此外,若在 9515 連結埠以外的其他連結埠上開啟 ChromeDriver,則應在相同類別內修改 driver
方法以修改為相應的連結埠:
1use Facebook\WebDriver\Remote\RemoteWebDriver;23/**4 * Create the RemoteWebDriver instance.5 */6protected function driver(): RemoteWebDriver7{8 return RemoteWebDriver::create(9 'http://localhost:9515', DesiredCapabilities::chrome()10 );11}
1use Facebook\WebDriver\Remote\RemoteWebDriver;23/**4 * Create the RemoteWebDriver instance.5 */6protected function driver(): RemoteWebDriver7{8 return RemoteWebDriver::create(9 'http://localhost:9515', DesiredCapabilities::chrome()10 );11}
處理環境
若要在執行測試時強制讓 Dusk 使用自己的環境檔,請在專案根目錄下建立一個 .env.dusk.{environment}
檔案。舉例來說,若會在 local
環境下執行 dusk
,請建立 .env.dusk.local
檔案。
執行測試時,Dusk 會備份 .env
檔,並將 Dusk 環境檔重新命名為 .env
。測試完成後,會恢復原本的 .env
檔。
「瀏覽器」基礎
建立瀏覽器
要開始使用瀏覽器,我們先來建立一個用來認證能否登入網站的測試。產生測試後,我們就可以修改該測試、前往登入頁、輸入帳號密碼、並點擊「登入」按鈕。要建立瀏覽器實體,可在 Dusk 測試內呼叫 browser
方法:
1<?php23use App\Models\User;4use Illuminate\Foundation\Testing\DatabaseMigrations;5use Laravel\Dusk\Browser;67uses(DatabaseMigrations::class);89test('basic example', function () {10 $user = User::factory()->create([12 ]);1314 $this->browse(function (Browser $browser) use ($user) {15 $browser->visit('/login')16 ->type('email', $user->email)17 ->type('password', 'password')18 ->press('Login')19 ->assertPathIs('/home');20 });21});
1<?php23use App\Models\User;4use Illuminate\Foundation\Testing\DatabaseMigrations;5use Laravel\Dusk\Browser;67uses(DatabaseMigrations::class);89test('basic example', function () {10 $user = User::factory()->create([12 ]);1314 $this->browse(function (Browser $browser) use ($user) {15 $browser->visit('/login')16 ->type('email', $user->email)17 ->type('password', 'password')18 ->press('Login')19 ->assertPathIs('/home');20 });21});
1<?php23namespace Tests\Browser;45use App\Models\User;6use Illuminate\Foundation\Testing\DatabaseMigrations;7use Laravel\Dusk\Browser;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 use DatabaseMigrations;1314 /**15 * A basic browser test example.16 */17 public function test_basic_example(): void18 {19 $user = User::factory()->create([21 ]);2223 $this->browse(function (Browser $browser) use ($user) {24 $browser->visit('/login')25 ->type('email', $user->email)26 ->type('password', 'password')27 ->press('Login')28 ->assertPathIs('/home');29 });30 }31}
1<?php23namespace Tests\Browser;45use App\Models\User;6use Illuminate\Foundation\Testing\DatabaseMigrations;7use Laravel\Dusk\Browser;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 use DatabaseMigrations;1314 /**15 * A basic browser test example.16 */17 public function test_basic_example(): void18 {19 $user = User::factory()->create([21 ]);2223 $this->browse(function (Browser $browser) use ($user) {24 $browser->visit('/login')25 ->type('email', $user->email)26 ->type('password', 'password')27 ->press('Login')28 ->assertPathIs('/home');29 });30 }31}
如上所見,browser
方法接受一個閉包。Dusk 會自動將瀏覽器實體傳入該閉包內,瀏覽器實體是用來與網站互動以及用來進行 Assertion 的主要物件。
建立多個瀏覽器
有時候,我們需要建立多個瀏覽器來正確地進行測試。舉例來說,在測試與 WebSocket 互動的聊天畫面時可能會需要多個瀏覽器。若要建立多個瀏覽器,只需要將多個瀏覽器引數加到提供給 browser
方法的閉包上即可:
1$this->browse(function (Browser $first, Browser $second) {2 $first->loginAs(User::find(1))3 ->visit('/home')4 ->waitForText('Message');56 $second->loginAs(User::find(2))7 ->visit('/home')8 ->waitForText('Message')9 ->type('message', 'Hey Taylor')10 ->press('Send');1112 $first->waitForText('Hey Taylor')13 ->assertSee('Jeffrey Way');14});
1$this->browse(function (Browser $first, Browser $second) {2 $first->loginAs(User::find(1))3 ->visit('/home')4 ->waitForText('Message');56 $second->loginAs(User::find(2))7 ->visit('/home')8 ->waitForText('Message')9 ->type('message', 'Hey Taylor')10 ->press('Send');1112 $first->waitForText('Hey Taylor')13 ->assertSee('Jeffrey Way');14});
導航
visit
方法可用來在網站內導航到特定的 URI 上:
1$browser->visit('/login');
1$browser->visit('/login');
可以使用 visitRoute
方法來導航到命名路由:
1$browser->visitRoute($routeName, $parameters);
1$browser->visitRoute($routeName, $parameters);
可以使用 back
與 forward
方法來導航到「上一頁」與「下一頁」:
1$browser->back();23$browser->forward();
1$browser->back();23$browser->forward();
可以使用 refresh
方法來重新整理頁面:
1$browser->refresh();
1$browser->refresh();
縮放瀏覽器視窗
可以使用 resize
方法來調整瀏覽器視窗的大小:
1$browser->resize(1920, 1080);
1$browser->resize(1920, 1080);
maximize
方法可用來最大化瀏覽器視窗:
1$browser->maximize();
1$browser->maximize();
fitContent
方法會將瀏覽器視窗縮放到符合其內容的大小:
1$browser->fitContent();
1$browser->fitContent();
當測試失敗時,Dusk 會自動縮放瀏覽器視窗來符合其內容,以進行截圖。可以通過在測試內呼叫 disableFitOnFailure
方法來禁用此功能:
1$browser->disableFitOnFailure();
1$browser->disableFitOnFailure();
可以使用 move
方法來將瀏覽器視窗移動到畫面上的不同位置:
1$browser->move($x = 100, $y = 100);
1$browser->move($x = 100, $y = 100);
瀏覽器 Macro
若想定義可在各個測試內重複使用的自訂瀏覽器方法,可使用 Browser
類別上的 macro
方法。通常來說,該方法應在某個 Service Provider 的 boot
方法內呼叫:
1<?php23namespace App\Providers;45use Illuminate\Support\ServiceProvider;6use Laravel\Dusk\Browser;78class DuskServiceProvider extends ServiceProvider9{10 /**11 * Register Dusk's browser macros.12 */13 public function boot(): void14 {15 Browser::macro('scrollToElement', function (string $element = null) {16 $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");1718 return $this;19 });20 }21}
1<?php23namespace App\Providers;45use Illuminate\Support\ServiceProvider;6use Laravel\Dusk\Browser;78class DuskServiceProvider extends ServiceProvider9{10 /**11 * Register Dusk's browser macros.12 */13 public function boot(): void14 {15 Browser::macro('scrollToElement', function (string $element = null) {16 $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");1718 return $this;19 });20 }21}
macro
方法接受一個名稱作為其第一個引數,以及閉包作為其第二個引數。當在 Browser
實體上以方法呼叫該 Macro 時,會執行該 Macro 的閉包:
1$this->browse(function (Browser $browser) use ($user) {2 $browser->visit('/pay')3 ->scrollToElement('#credit-card-details')4 ->assertSee('Enter Credit Card Details');5});
1$this->browse(function (Browser $browser) use ($user) {2 $browser->visit('/pay')3 ->scrollToElement('#credit-card-details')4 ->assertSee('Enter Credit Card Details');5});
登入認證
一般來說,我們會需要測試需要登入的頁面。可以使用 Dusk 的 loginAs
方法來避免每個測試都需要處理網站的登入畫面。loginAs
方法接受 Authenticatable Model 所關聯的主索引鍵,或是 Authenticatable Model 實體:
1use App\Models\User;2use Laravel\Dusk\Browser;34$this->browse(function (Browser $browser) {5 $browser->loginAs(User::find(1))6 ->visit('/home');7});
1use App\Models\User;2use Laravel\Dusk\Browser;34$this->browse(function (Browser $browser) {5 $browser->loginAs(User::find(1))6 ->visit('/home');7});
使用 loginAs
方法後,在該檔案內所有的測試都將使用該使用者 Session。
Cookie
可以使用 cookie
方法來取得或設定加密的 Cookie 值。預設情況下,Laravel 所建立的所有 Cookie 都是經過加密的:
1$browser->cookie('name');23$browser->cookie('name', 'Taylor');
1$browser->cookie('name');23$browser->cookie('name', 'Taylor');
可以使用 plainCookie
方法來取得或設定未加密的 Cookie 值:
1$browser->plainCookie('name');23$browser->plainCookie('name', 'Taylor');
1$browser->plainCookie('name');23$browser->plainCookie('name', 'Taylor');
可以使用 deleteCookie
方法來刪除給定的 Cookie:
1$browser->deleteCookie('name');
1$browser->deleteCookie('name');
執行 JavaScript
可以使用 script
方法來在瀏覽器內執行任意的 JavaScript 陳述式:
1$browser->script('document.documentElement.scrollTop = 0');23$browser->script([4 'document.body.scrollTop = 0',5 'document.documentElement.scrollTop = 0',6]);78$output = $browser->script('return window.location.pathname');
1$browser->script('document.documentElement.scrollTop = 0');23$browser->script([4 'document.body.scrollTop = 0',5 'document.documentElement.scrollTop = 0',6]);78$output = $browser->script('return window.location.pathname');
Taking a Screenshot
可以使用 screenshot
方法來截圖,並將截圖保存為給定的檔案名稱。所有的截圖都會保存在 tests/Browser/screenshots
目錄內:
1$browser->screenshot('filename');
1$browser->screenshot('filename');
responsiveScreenshots
方法可用來在各個 Breakpoint 上截取一系列的截圖:
1$browser->responsiveScreenshots('filename');
1$browser->responsiveScreenshots('filename');
The screenshotElement
method may be used to take a screenshot of a specific element on the page:
1$browser->screenshotElement('#selector', 'filename');
1$browser->screenshotElement('#selector', 'filename');
Storing Console Output to Disk
可以使用 storeConsoleLog
方法來將目前瀏覽器的主控台輸出以給定的檔案名稱寫入到磁碟內。主控台輸出會保存在 tests/Browser/console
目錄內:
1$browser->storeConsoleLog('filename');
1$browser->storeConsoleLog('filename');
Storing Page Source to Disk
可以使用 storeSource
方法來將目前頁面的原始碼以給定的檔案名稱寫入到磁碟內。頁面原始碼會保存在 tests/Browser/source
目錄內:
1$browser->storeSource('filename');
1$browser->storeSource('filename');
與元素互動
Dusk 選擇器
在撰寫 Dusk 測試時,選擇一個好的 CSS 選擇器來與元素互動是最難的一部分。日子一天天過去,當前端有更改時,若有像下列這樣的 CSS 選擇器就有可能讓測試失敗:
1// HTML...23<button>Login</button>45// Test...67$browser->click('.login-page .container div > button');
1// HTML...23<button>Login</button>45// Test...67$browser->click('.login-page .container div > button');
使用 Dusk 選擇器,就能讓開發人員更專注於撰寫有效的測試,而不是記住 CSS 選擇器。若要定義選擇請,請在 HTML 元素內加上 dusk
屬性。接著,當與 Dusk 瀏覽器互動時,請在該選擇器前方加上 @
來在測試內操作該元素:
1// HTML...23<button dusk="login-button">Login</button>45// Test...67$browser->click('@login-button');
1// HTML...23<button dusk="login-button">Login</button>45// Test...67$browser->click('@login-button');
若有需要,可以使用 selectorHtmlAttribute
方法來自定 Dusk Selector 使用的 HTML 屬性。一般來說,應在專案中 AppServiceProvider
內 boot
方法中呼叫該方法:
1use Laravel\Dusk\Dusk;23Dusk::selectorHtmlAttribute('data-dusk');
1use Laravel\Dusk\Dusk;23Dusk::selectorHtmlAttribute('data-dusk');
Text, Values, and Attributes
Retrieving and Setting Values
Dusk 內提供了數種可與目前頁面上元素的值、顯示文字、與屬性互動的方法。舉例來說,若要在某個符合給定 CSS 或 Dusk 選擇器的元素上取得該元素的「值 (Value)」,可使用 value
方法:
1// Retrieve the value...2$value = $browser->value('selector');34// Set the value...5$browser->value('selector', 'value');
1// Retrieve the value...2$value = $browser->value('selector');34// Set the value...5$browser->value('selector', 'value');
可以使用 inputValue
方法來取得某個給定欄位名稱之 input 元素的「值 (Value)」:
1$value = $browser->inputValue('field');
1$value = $browser->inputValue('field');
取得文字
可使用 text
方法來取得符合給定選擇器之元素的顯示文字:
1$text = $browser->text('selector');
1$text = $browser->text('selector');
取得屬性
最後,可使用 attribute
方法來取得符合給定選擇器之元素的屬性值:
1$attribute = $browser->attribute('selector', 'value');
1$attribute = $browser->attribute('selector', 'value');
與表單互動
鍵入值
Dusk 提供了多種與表單以及 Input 元素互動的方法。首先,來看看一個在 Input 欄位內鍵入文字的例子:
請注意這裡,雖然可將 CSS 選擇器傳入 type
方法,但並不需特別傳入。若未提供 CSS 選擇器,則 Dusk 會搜尋符合給定 name
屬性的 input
或 textarea
欄位。
若要在不將其原本內容清除的情況下將文字附加在最後面,可以使用 append
方法:
1$browser->type('tags', 'foo')2 ->append('tags', ', bar, baz');
1$browser->type('tags', 'foo')2 ->append('tags', ', bar, baz');
可以使用 clear
方法來清除某個 Input 的值:
1$browser->clear('email');
1$browser->clear('email');
可以使用 typeSlowly
方法來讓 Dusk 輸入得慢一點。預設情況下,Dusk 會在每個按鍵間暫停 100 毫秒。若要自訂按鍵按下間的時間,可將適當的毫秒數作為第三個引數傳給該方法:
1$browser->typeSlowly('mobile', '+1 (202) 555-5555');23$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
1$browser->typeSlowly('mobile', '+1 (202) 555-5555');23$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
可以使用 appendSlowly
方法來慢慢地將文字附加到最後:
1$browser->type('tags', 'foo')2 ->appendSlowly('tags', ', bar, baz');
1$browser->type('tags', 'foo')2 ->appendSlowly('tags', ', bar, baz');
下拉選單
若要在 select
元素上選擇可用的值,可使用 select
方法。與 type
方法類似,select
方法並不要求要提供完整的 CSS 選擇器。將值傳給 select
方法時,應傳入底層的選項值而非顯示的文字:
1$browser->select('size', 'Large');
1$browser->select('size', 'Large');
也可以通過省略第二個引數來隨機選擇選項:
1$browser->select('size');
1$browser->select('size');
在 select
方法的第二個引數中使用陣列,就可以選擇多個選項:
1$browser->select('categories', ['Art', 'Music']);
1$browser->select('categories', ['Art', 'Music']);
多選框
若要「勾選」多選框,可使用 check
方法。與其他 Input 有關的方法類似,並不需要傳入完整的 CSS 選擇器。若找不到對應的 CSS 選擇器,Dusk 會自動搜尋符合 name
屬性的多選框:
1$browser->check('terms');
1$browser->check('terms');
可使用 uncheck
方法來「取消勾選」多選框:
1$browser->uncheck('terms');
1$browser->uncheck('terms');
單選框
若要「勾選」radio
單選框,可使用 check
方法。與其他 Input 有關的方法類似,並不需要傳入完整的 CSS 選擇器。若找不到對應的 CSS 選擇器,Dusk 會自動搜尋符合 name
屬性的 radio
單選框:
1$browser->radio('size', 'large');
1$browser->radio('size', 'large');
附加檔案
可使用 attach
方法來將檔案附加到 file
Input 元素上。與其他 Input 有關的方法類似,並不需要傳入完整的 CSS 選擇器。若找不到對應的 CSS 選擇器,Dusk 會自動搜尋符合 name
屬性的 file
Input:
1$browser->attach('photo', __DIR__.'/photos/mountains.png');
1$browser->attach('photo', __DIR__.'/photos/mountains.png');
要使用 attach 函式,伺服器上必須有安裝 Zip
PHP 擴充套件並已啟用。
按下按鈕
press
方法可用來點擊頁面上的按鈕元素。傳給 press
方法的引數可以是按鈕的顯示文字,也可以是 CSS / Dusk 選擇器:
1$browser->press('Login');
1$browser->press('Login');
在送出表單時,許多網站會在按鈕按下的時候禁用表單的送出按鈕,並在表單送出的 HTTP 請求完成後重新啟用該按鈕。若要按下按鈕並等待該按鈕重新啟用,可使用 pressAndWaitFor
方法:
1// Press the button and wait a maximum of 5 seconds for it to be enabled...2$browser->pressAndWaitFor('Save');34// Press the button and wait a maximum of 1 second for it to be enabled...5$browser->pressAndWaitFor('Save', 1);
1// Press the button and wait a maximum of 5 seconds for it to be enabled...2$browser->pressAndWaitFor('Save');34// Press the button and wait a maximum of 1 second for it to be enabled...5$browser->pressAndWaitFor('Save', 1);
點擊連結
若要點擊連結,可使用瀏覽器實體上的 clickLink
方法。clickLink
方法會點擊有給定顯示文字的連結:
1$browser->clickLink($linkText);
1$browser->clickLink($linkText);
可使用 seeLink
方法來判斷給定的顯示文字是否在頁面上可見:
1if ($browser->seeLink($linkText)) {2 // ...3}
1if ($browser->seeLink($linkText)) {2 // ...3}
該方法需要與 jQuery 互動。若頁面上沒有 jQuery 可用,則 Dusk 會自動將 jQuery 插入到頁面上以在測試期間使用。
Using the Keyboard
比起使用一般的 type
方法,keys
方法提供了可對給定元素進行一系列更複雜輸入的能力。舉例來說,可以讓 Dusk 在輸入數值的時候按著某個輔助按鍵。在這個範例中,於符合給定選擇器的元素內輸入 taylor
文字時,會按著 Shift
鍵。輸入完 taylor
後,swift
會在不按下任何輔助按鍵的情況下輸入:
1$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
1$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
keys
方法的另一個實用用途是給主要 CSS 選擇器傳送一組「鍵盤快捷鍵」:
1$browser->keys('.app', ['{command}', 'j']);
1$browser->keys('.app', ['{command}', 'j']);
所有的輔助按鍵,如 {command}
都以 {}
字元來進行包裝,且符合 Facebook\WebDriver\WebDriverKeys
中所定義的常數值。可在 GitHub 上找到這些常數值。
流暢地使用鍵盤
Dusk 還提供了一個 withKeyboard
方法,讓你能使用 Laravel\Dusk\Keyboard
類別來流暢地進行複雜的鍵盤動作。Keyboard
類別提供了 press
, release
, type
與 pause
方法:
1use Laravel\Dusk\Keyboard;23$browser->withKeyboard(function (Keyboard $keyboard) {4 $keyboard->press('c')5 ->pause(1000)6 ->release('c')7 ->type(['c', 'e', 'o']);8});
1use Laravel\Dusk\Keyboard;23$browser->withKeyboard(function (Keyboard $keyboard) {4 $keyboard->press('c')5 ->pause(1000)6 ->release('c')7 ->type(['c', 'e', 'o']);8});
鍵盤巨集
若想定義可在各個測試內重複使用的自訂鍵盤動作,可使用 Keyboard
類別上的 macro
方法。一半來說,該方法應在某個 Service Provider 的 boot
方法內呼叫:
1<?php23namespace App\Providers;45use Facebook\WebDriver\WebDriverKeys;6use Illuminate\Support\ServiceProvider;7use Laravel\Dusk\Keyboard;8use Laravel\Dusk\OperatingSystem;910class DuskServiceProvider extends ServiceProvider11{12 /**13 * Register Dusk's browser macros.14 */15 public function boot(): void16 {17 Keyboard::macro('copy', function (string $element = null) {18 $this->type([19 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',20 ]);2122 return $this;23 });2425 Keyboard::macro('paste', function (string $element = null) {26 $this->type([27 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',28 ]);2930 return $this;31 });32 }33}
1<?php23namespace App\Providers;45use Facebook\WebDriver\WebDriverKeys;6use Illuminate\Support\ServiceProvider;7use Laravel\Dusk\Keyboard;8use Laravel\Dusk\OperatingSystem;910class DuskServiceProvider extends ServiceProvider11{12 /**13 * Register Dusk's browser macros.14 */15 public function boot(): void16 {17 Keyboard::macro('copy', function (string $element = null) {18 $this->type([19 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',20 ]);2122 return $this;23 });2425 Keyboard::macro('paste', function (string $element = null) {26 $this->type([27 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',28 ]);2930 return $this;31 });32 }33}
macro
方法接受一個名稱作為其第一個引數,以及閉包作為其第二個引數。當在 Keyboard
實體上以方法呼叫該 Macro 時,會執行該 Macro 的閉包:
1$browser->click('@textarea')2 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())3 ->click('@another-textarea')4 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());
1$browser->click('@textarea')2 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())3 ->click('@another-textarea')4 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());
Using the Mouse
Clicking on Elements
可使用 click
方法來點擊符合給定 CSS 或 Dusk 選擇器的元素:
1$browser->click('.selector');
1$browser->click('.selector');
可使用 clickAtXPath
方法來點擊符合給定 XPath 運算式的元素:
1$browser->clickAtXPath('//div[@class = "selector"]');
1$browser->clickAtXPath('//div[@class = "selector"]');
可使用 clickAtPoint
方法來點擊在相對於瀏覽器檢視區域上,符合給定座標點上最上層的元素:
1$browser->clickAtPoint($x = 0, $y = 0);
1$browser->clickAtPoint($x = 0, $y = 0);
可使用 doubleClick
方法來模擬使用滑鼠點兩下:
1$browser->doubleClick();23$browser->doubleClick('.selector');
1$browser->doubleClick();23$browser->doubleClick('.selector');
可使用 rightClick
方法來模擬按滑鼠右鍵:
1$browser->rightClick();23$browser->rightClick('.selector');
1$browser->rightClick();23$browser->rightClick('.selector');
可使用 clickAndHold
方法來模擬按下滑鼠按鈕並保持按下。若接著呼叫 releaseMouse
方法,則會取消這個行為並放開滑鼠按鈕:
1$browser->clickAndHold('.selector');23$browser->clickAndHold()4 ->pause(1000)5 ->releaseMouse();
1$browser->clickAndHold('.selector');23$browser->clickAndHold()4 ->pause(1000)5 ->releaseMouse();
controlClick
方法可用來在瀏覽器上模擬 ctrl+click
事件:
1$browser->controlClick();23$browser->controlClick('.selector');
1$browser->controlClick();23$browser->controlClick('.selector');
滑鼠移至上方
當需要將滑鼠移至符合給定 CSS 或 Dusk 選擇器的元素上時,可使用 mouseover
方法:
1$browser->mouseover('.selector');
1$browser->mouseover('.selector');
Drag and Drop
可使用 drag
方法來將符合給定選擇器元素拖曳至另一個元素上:
1$browser->drag('.from-selector', '.to-selector');
1$browser->drag('.from-selector', '.to-selector');
或者,也可以將某個元素在單一方向上拖曳:
1$browser->dragLeft('.selector', $pixels = 10);2$browser->dragRight('.selector', $pixels = 10);3$browser->dragUp('.selector', $pixels = 10);4$browser->dragDown('.selector', $pixels = 10);
1$browser->dragLeft('.selector', $pixels = 10);2$browser->dragRight('.selector', $pixels = 10);3$browser->dragUp('.selector', $pixels = 10);4$browser->dragDown('.selector', $pixels = 10);
最後,可以依照給定偏移值來拖曳元素:
1$browser->dragOffset('.selector', $x = 10, $y = 10);
1$browser->dragOffset('.selector', $x = 10, $y = 10);
JavaScript 對話方塊
Dusk 提供了多種與 JavaScript 對話方塊互動的方法。舉例來說,可以使用 waitForDialog
方法來等待 JavaScript 對話方塊出現。該方法可接收一個可選的引數來判斷要等幾秒讓該對話方塊顯示出來:
1$browser->waitForDialog($seconds = null);
1$browser->waitForDialog($seconds = null);
可使用 assertDialogOpened
方法來判斷某個對話方塊是否已顯示,且包含給定的訊息:
1$browser->assertDialogOpened('Dialog message');
1$browser->assertDialogOpened('Dialog message');
若該 JavaScript 對話方塊包含輸入提示,可使用 typeInDialog
方法來在該提示中輸入數值:
1$browser->typeInDialog('Hello World');
1$browser->typeInDialog('Hello World');
若要點擊「確定」按鈕來關閉開啟的 JavaScript 對話方塊,可以叫用 acceptDialog
方法:
1$browser->acceptDialog();
1$browser->acceptDialog();
若要點擊「取消」按鈕來關閉開啟的 JavaScript 對話方塊,可以叫用 dismissDialog
方法:
1$browser->dismissDialog();
1$browser->dismissDialog();
處理 IFrame
若有需要操作 iframe 中的元素,可以使用 withinFrame
方法。在提供給 withinFrame
方法的 Closure 中,所有的元素互動都會被限制在指定 iframe 的範圍內:
1$browser->withinFrame('#credit-card-details', function ($browser) {2 $browser->type('input[name="cardnumber"]', '4242424242424242')3 ->type('input[name="exp-date"]', '1224')4 ->type('input[name="cvc"]', '123')5 ->press('Pay');6});
1$browser->withinFrame('#credit-card-details', function ($browser) {2 $browser->type('input[name="cardnumber"]', '4242424242424242')3 ->type('input[name="exp-date"]', '1224')4 ->type('input[name="cvc"]', '123')5 ->press('Pay');6});
區域性選擇器
有的時候,我們可能會想把多個操作限制到某個特定選擇器裡面。舉例來說,我們在判斷某段文字是否有出現時,可能只想在某個表格內檢查,並在檢查完畢後接著在該表格內點擊某個按鈕。可以使用 with
方法來達成。在提供給 with
方法的閉包內所進行的操作都會被限制在某個選擇器之內:
1$browser->with('.table', function (Browser $table) {2 $table->assertSee('Hello World')3 ->clickLink('Delete');4});
1$browser->with('.table', function (Browser $table) {2 $table->assertSee('Hello World')3 ->clickLink('Delete');4});
某些時候,我們可能需要在目前的 Scope 外執行 Assertion。可以使用 elsewhere
與 elsewhereWhenAvailable
方法來進行:
1$browser->with('.table', function (Browser $table) {2 // Current scope is `body .table`...34 $browser->elsewhere('.page-title', function (Browser $title) {5 // Current scope is `body .page-title`...6 $title->assertSee('Hello World');7 });89 $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {10 // Current scope is `body .page-title`...11 $title->assertSee('Hello World');12 });13});
1$browser->with('.table', function (Browser $table) {2 // Current scope is `body .table`...34 $browser->elsewhere('.page-title', function (Browser $title) {5 // Current scope is `body .page-title`...6 $title->assertSee('Hello World');7 });89 $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {10 // Current scope is `body .page-title`...11 $title->assertSee('Hello World');12 });13});
Waiting for Elements
在測試使用了大量 JavaScript 的網站時,常常會需要「等待」特定元素或資料出現後才能繼續進行測試。在 Dusk 中可以輕鬆做到。只需要使用幾個方法,就可以等待元素顯示在頁面上,或是等待某個給定的 JavaScript 運算式取值變為 true
。
等待
若只是需要將測試暫停幾毫秒,可使用 pause
方法:
1$browser->pause(1000);
1$browser->pause(1000);
若只想在某個給定條件為 true
時暫停測試,可使用 pauseIf
方法:
1$browser->pauseIf(App::environment('production'), 1000);
1$browser->pauseIf(App::environment('production'), 1000);
類似的,若只想在某個給定條件不為 true
時暫停測試,可使用 pauseUnless
方法:
1$browser->pauseUnless(App::environment('testing'), 1000);
1$browser->pauseUnless(App::environment('testing'), 1000);
Waiting for Selectors
waitFor
方法可用來暫停執行測試,並等到符合給定 CSS 或 Dusk 選擇器的元素顯示在頁面上。預設情況下,該方法會最多會暫停測試五秒,超過則會擲回例外。若有需要,可以將自訂的逾時閥值傳入為該方法的第二個引數:
1// Wait a maximum of five seconds for the selector...2$browser->waitFor('.selector');34// Wait a maximum of one second for the selector...5$browser->waitFor('.selector', 1);
1// Wait a maximum of five seconds for the selector...2$browser->waitFor('.selector');34// Wait a maximum of one second for the selector...5$browser->waitFor('.selector', 1);
也可以等待某個符合給定選擇器的元素出現給定文字:
1// Wait a maximum of five seconds for the selector to contain the given text...2$browser->waitForTextIn('.selector', 'Hello World');34// Wait a maximum of one second for the selector to contain the given text...5$browser->waitForTextIn('.selector', 'Hello World', 1);
1// Wait a maximum of five seconds for the selector to contain the given text...2$browser->waitForTextIn('.selector', 'Hello World');34// Wait a maximum of one second for the selector to contain the given text...5$browser->waitForTextIn('.selector', 'Hello World', 1);
也可以等待某個符合給定選擇器的元素消失在頁面上:
1// Wait a maximum of five seconds until the selector is missing...2$browser->waitUntilMissing('.selector');34// Wait a maximum of one second until the selector is missing...5$browser->waitUntilMissing('.selector', 1);
1// Wait a maximum of five seconds until the selector is missing...2$browser->waitUntilMissing('.selector');34// Wait a maximum of one second until the selector is missing...5$browser->waitUntilMissing('.selector', 1);
或者,也可以等待給定的選擇器為 Enabled 或 Disabled:
1// Wait a maximum of five seconds until the selector is enabled...2$browser->waitUntilEnabled('.selector');34// Wait a maximum of one second until the selector is enabled...5$browser->waitUntilEnabled('.selector', 1);67// Wait a maximum of five seconds until the selector is disabled...8$browser->waitUntilDisabled('.selector');910// Wait a maximum of one second until the selector is disabled...11$browser->waitUntilDisabled('.selector', 1);
1// Wait a maximum of five seconds until the selector is enabled...2$browser->waitUntilEnabled('.selector');34// Wait a maximum of one second until the selector is enabled...5$browser->waitUntilEnabled('.selector', 1);67// Wait a maximum of five seconds until the selector is disabled...8$browser->waitUntilDisabled('.selector');910// Wait a maximum of one second until the selector is disabled...11$browser->waitUntilDisabled('.selector', 1);
可用時進入選擇器的 Scope
有時候我們可能會想等待符合給定選擇器的元素出現在頁面上後再接著與該元素互動。舉例來說,我們可能會想等待某個 Modal 視窗出現,然後在該 Modal 內點擊「OK」按鈕。可以使用 whenAvailable
方法來完成。在給定閉包內進行的所有元素操作都會被限制在原始選擇器的作用範圍內:
1$browser->whenAvailable('.modal', function (Browser $modal) {2 $modal->assertSee('Hello World')3 ->press('OK');4});
1$browser->whenAvailable('.modal', function (Browser $modal) {2 $modal->assertSee('Hello World')3 ->press('OK');4});
Waiting for Text
可使用 waitForText
方法來等待給定文字顯示在頁面上:
1// Wait a maximum of five seconds for the text...2$browser->waitForText('Hello World');34// Wait a maximum of one second for the text...5$browser->waitForText('Hello World', 1);
1// Wait a maximum of five seconds for the text...2$browser->waitForText('Hello World');34// Wait a maximum of one second for the text...5$browser->waitForText('Hello World', 1);
可以使用 waitUntilMissingText
方法來等待某個正在顯示的文字從頁面上移除:
1// Wait a maximum of five seconds for the text to be removed...2$browser->waitUntilMissingText('Hello World');34// Wait a maximum of one second for the text to be removed...5$browser->waitUntilMissingText('Hello World', 1);
1// Wait a maximum of five seconds for the text to be removed...2$browser->waitUntilMissingText('Hello World');34// Wait a maximum of one second for the text to be removed...5$browser->waitUntilMissingText('Hello World', 1);
Waiting for Links
可使用 waitForLink
方法來等待給定連結文字顯示在頁面上:
1// Wait a maximum of five seconds for the link...2$browser->waitForLink('Create');34// Wait a maximum of one second for the link...5$browser->waitForLink('Create', 1);
1// Wait a maximum of five seconds for the link...2$browser->waitForLink('Create');34// Wait a maximum of one second for the link...5$browser->waitForLink('Create', 1);
Waiting for Inputs
waitForInput
方法可用於等待給定輸入欄位顯示在頁面上:
1// Wait a maximum of five seconds for the input...2$browser->waitForInput($field);34// Wait a maximum of one second for the input...5$browser->waitForInput($field, 1);
1// Wait a maximum of five seconds for the input...2$browser->waitForInput($field);34// Wait a maximum of one second for the input...5$browser->waitForInput($field, 1);
Waiting on the Page Location
在進行如 $browser->assertPathIs('/home')
這種路徑 Assertion 時,如果 window.location.pathname
是非同步更新的,則該 Assertion 可能會失敗。可以使用 waitForLocation
方法來等待路徑為給定的值:
1$browser->waitForLocation('/secret');
1$browser->waitForLocation('/secret');
也可以使用 waitForLocation
方法來等待目前視窗的路徑符合完整的 URL:
1$browser->waitForLocation('https://example.com/path');
1$browser->waitForLocation('https://example.com/path');
也可以等待 命名路由 的位置:
1$browser->waitForRoute($routeName, $parameters);
1$browser->waitForRoute($routeName, $parameters);
等待頁面重新整理
若有需要在執行特定動作前等待頁面重新整理,請使用 waitForReload
方法:
1use Laravel\Dusk\Browser;23$browser->waitForReload(function (Browser $browser) {4 $browser->press('Submit');5})6->assertSee('Success!');
1use Laravel\Dusk\Browser;23$browser->waitForReload(function (Browser $browser) {4 $browser->press('Submit');5})6->assertSee('Success!');
由於我們通常會在點擊按鈕後等待頁面重新整理,因此可以使用更方便的 clickAndWaitForReload
方法:
1$browser->clickAndWaitForReload('.selector')2 ->assertSee('something');
1$browser->clickAndWaitForReload('.selector')2 ->assertSee('something');
Waiting on JavaScript Expressions
有時候,我們可能會想暫停測試並等待某個給定的 JavaScript 運算式取值為 true
。可使用 waitUntil
方法來輕鬆達成。將運算式傳給該方法時,不需要包含 return
關鍵字或結尾的分號:
1// Wait a maximum of five seconds for the expression to be true...2$browser->waitUntil('App.data.servers.length > 0');34// Wait a maximum of one second for the expression to be true...5$browser->waitUntil('App.data.servers.length > 0', 1);
1// Wait a maximum of five seconds for the expression to be true...2$browser->waitUntil('App.data.servers.length > 0');34// Wait a maximum of one second for the expression to be true...5$browser->waitUntil('App.data.servers.length > 0', 1);
Waiting on Vue Expressions
可使用 waitUntilVue
與 waitUntilVueIsNot
方法來等待給定的 Vue 元件 屬性具有給定的值:
1// Wait until the component attribute contains the given value...2$browser->waitUntilVue('user.name', 'Taylor', '@user');34// Wait until the component attribute doesn't contain the given value...5$browser->waitUntilVueIsNot('user.name', null, '@user');
1// Wait until the component attribute contains the given value...2$browser->waitUntilVue('user.name', 'Taylor', '@user');34// Wait until the component attribute doesn't contain the given value...5$browser->waitUntilVueIsNot('user.name', null, '@user');
Waiting for JavaScript Events
waitForEvent
方法可用來暫停執行測試,直到發生了某個 JavaScript 事件:
1$browser->waitForEvent('load');
1$browser->waitForEvent('load');
會附加一個 Event Listener 到目前的 Scope 上,預設為 body
元素。在使用限定範圍的 Selector 時,則會將該 Event Listener 附加到符合的元素上:
1$browser->with('iframe', function (Browser $iframe) {2 // Wait for the iframe's load event...3 $iframe->waitForEvent('load');4});
1$browser->with('iframe', function (Browser $iframe) {2 // Wait for the iframe's load event...3 $iframe->waitForEvent('load');4});
也可以使用 waitForEvent
方法的第二個引數來提供選擇器,以將 Event Listener 附加到特定的元素上:
1$browser->waitForEvent('load', '.selector');
1$browser->waitForEvent('load', '.selector');
也可以在 document
或 window
物件上等待事件:
1// Wait until the document is scrolled...2$browser->waitForEvent('scroll', 'document');34// Wait a maximum of five seconds until the window is resized...5$browser->waitForEvent('resize', 'window', 5);
1// Wait until the document is scrolled...2$browser->waitForEvent('scroll', 'document');34// Wait a maximum of five seconds until the window is resized...5$browser->waitForEvent('resize', 'window', 5);
Waiting With a Callback
在 Dusk 中,許多的「wait」方法都仰賴於底層的 waitUsing
方法。可以直接使用該方法來等待給定的閉包回傳 true
。waitUsing
方法接受等待最大秒數、閉包取值的時間間隔、閉包、以及一個可選的錯誤訊息:
1$browser->waitUsing(10, 1, function () use ($something) {2 return $something->isReady();3}, "Something wasn't ready in time.");
1$browser->waitUsing(10, 1, function () use ($something) {2 return $something->isReady();3}, "Something wasn't ready in time.");
Scrolling an Element Into View
有時候,我們可能沒辦法點擊某個元素,因為該元素在瀏覽器可視區域外。使用 scrollIntoView
方法可以滾動瀏覽器視窗,直到給定選擇器元素出現在顯示區內:
1$browser->scrollIntoView('.selector')2 ->click('.selector');
1$browser->scrollIntoView('.selector')2 ->click('.selector');
可用的 Assertion
Dusk 提供了多種可對網站進行的 Assertion。下面列出了所有可用的 Assertion:
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 assertScript assertSourceHas assertSourceMissing assertSeeLink assertDontSeeLink assertInputValue assertInputValueIsNot assertChecked assertNotChecked assertIndeterminate assertRadioSelected assertRadioNotSelected assertSelected assertNotSelected assertSelectHasOptions assertSelectMissingOptions assertSelectHasOption assertSelectMissingOption assertValue assertValueIsNot assertAttribute 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
判斷頁面標題符合給定文字:
1$browser->assertTitle($title);
1$browser->assertTitle($title);
assertTitleContains
判斷頁面標題包含給定文字:
1$browser->assertTitleContains($title);
1$browser->assertTitleContains($title);
assertUrlIs
判斷目前 URL (不含查詢字串 Query String) 符合給定字串:
1$browser->assertUrlIs($url);
1$browser->assertUrlIs($url);
assertSchemeIs
判斷目前 URL 的協定 (Scheme) 符合給定協定:
1$browser->assertSchemeIs($scheme);
1$browser->assertSchemeIs($scheme);
assertSchemeIsNot
判斷目前的 URL 協定 (Scheme) 不符合給定協定:
1$browser->assertSchemeIsNot($scheme);
1$browser->assertSchemeIsNot($scheme);
assertHostIs
判斷目前 URL 的主機名稱 (Host) 符合給定主機名稱:
1$browser->assertHostIs($host);
1$browser->assertHostIs($host);
assertHostIsNot
判斷目前 URL 的主機名稱 (Host) 不符合給定主機名稱:
1$browser->assertHostIsNot($host);
1$browser->assertHostIsNot($host);
assertPortIs
判斷目前 URL 的連接埠 (Port) 符合給定連接埠:
1$browser->assertPortIs($port);
1$browser->assertPortIs($port);
assertPortIsNot
判斷目前 URL 的連接埠 (Port) 不符合給定連接埠:
1$browser->assertPortIsNot($port);
1$browser->assertPortIsNot($port);
assertPathBeginsWith
判斷目前 URL 的路徑 (Path) 以給定路徑開始:
1$browser->assertPathBeginsWith('/home');
1$browser->assertPathBeginsWith('/home');
assertPathEndsWith
Assert that the current URL path ends with the given path:
1$browser->assertPathEndsWith('/home');
1$browser->assertPathEndsWith('/home');
assertPathContains
Assert that the current URL path contains the given path:
1$browser->assertPathContains('/home');
1$browser->assertPathContains('/home');
assertPathIs
判斷目前路徑 (Path) 符合給定路徑:
1$browser->assertPathIs('/home');
1$browser->assertPathIs('/home');
assertPathIsNot
判斷目前路徑不符合給定路徑:
1$browser->assertPathIsNot('/home');
1$browser->assertPathIsNot('/home');
assertRouteIs
判斷目前 URL 符合給定的 命名路由 URL:
1$browser->assertRouteIs($name, $parameters);
1$browser->assertRouteIs($name, $parameters);
assertQueryStringHas
判斷查詢字串 (Query String) 有包含給定參數:
1$browser->assertQueryStringHas($name);
1$browser->assertQueryStringHas($name);
判斷查詢字串有包含給定參數,並符合給定的值:
1$browser->assertQueryStringHas($name, $value);
1$browser->assertQueryStringHas($name, $value);
assertQueryStringMissing
判斷查詢字串 (Query String) 不包含給定的參數:
1$browser->assertQueryStringMissing($name);
1$browser->assertQueryStringMissing($name);
assertFragmentIs
判斷 URL 目前的雜湊片段 (Hash Fragment) 符合給定的片段:
1$browser->assertFragmentIs('anchor');
1$browser->assertFragmentIs('anchor');
assertFragmentBeginsWith
判斷 URL 目前的雜湊片段 (Hash Fragment) 以給定的片段開始:
1$browser->assertFragmentBeginsWith('anchor');
1$browser->assertFragmentBeginsWith('anchor');
assertFragmentIsNot
判斷 URL 目前的雜湊片段 (Hash Fragment) 不符合給定的片段:
1$browser->assertFragmentIsNot('anchor');
1$browser->assertFragmentIsNot('anchor');
assertHasCookie
判斷 Cookie 中含有給定的加密 Cookie:
1$browser->assertHasCookie($name);
1$browser->assertHasCookie($name);
assertHasPlainCookie
判斷 Cookie 中含有給定的未加密 Cookie:
1$browser->assertHasPlainCookie($name);
1$browser->assertHasPlainCookie($name);
assertCookieMissing
判斷 Cookie 中不包含給定的加密 Cookie:
1$browser->assertCookieMissing($name);
1$browser->assertCookieMissing($name);
assertPlainCookieMissing
判斷 Cookie 中不包含給定的未加密 Cookie:
1$browser->assertPlainCookieMissing($name);
1$browser->assertPlainCookieMissing($name);
assertCookieValue
判斷加密 Cookie 為給定的值:
1$browser->assertCookieValue($name, $value);
1$browser->assertCookieValue($name, $value);
assertPlainCookieValue
判斷未加密 Cookie 為給定的值:
1$browser->assertPlainCookieValue($name, $value);
1$browser->assertPlainCookieValue($name, $value);
assertSee
判斷給定文字有出現在頁面上:
1$browser->assertSee($text);
1$browser->assertSee($text);
assertDontSee
判斷給定文字未出現在頁面上:
1$browser->assertDontSee($text);
1$browser->assertDontSee($text);
assertSeeIn
判斷給定文字出現在選擇器中:
1$browser->assertSeeIn($selector, $text);
1$browser->assertSeeIn($selector, $text);
assertDontSeeIn
判斷給定文字未出現在選擇器中:
1$browser->assertDontSeeIn($selector, $text);
1$browser->assertDontSeeIn($selector, $text);
assertSeeAnythingIn
判斷選擇器中有包含任何文字:
1$browser->assertSeeAnythingIn($selector);
1$browser->assertSeeAnythingIn($selector);
assertSeeNothingIn
判斷選擇器中未包含任何文字:
1$browser->assertSeeNothingIn($selector);
1$browser->assertSeeNothingIn($selector);
assertScript
判斷給定的 JavaScript 運算式取值為給定的值:
1$browser->assertScript('window.isLoaded')2 ->assertScript('document.readyState', 'complete');
1$browser->assertScript('window.isLoaded')2 ->assertScript('document.readyState', 'complete');
assertSourceHas
判斷給定的原始碼有出現在頁面上:
1$browser->assertSourceHas($code);
1$browser->assertSourceHas($code);
assertSourceMissing
判斷給定的原始碼未出現在頁面上:
1$browser->assertSourceMissing($code);
1$browser->assertSourceMissing($code);
assertSeeLink
判斷給定連結有出現在頁面上:
1$browser->assertSeeLink($linkText);
1$browser->assertSeeLink($linkText);
assertDontSeeLink
判斷給定連結未出現在頁面上:
1$browser->assertDontSeeLink($linkText);
1$browser->assertDontSeeLink($linkText);
assertInputValue
判斷給定的輸入欄位為給定值:
1$browser->assertInputValue($field, $value);
1$browser->assertInputValue($field, $value);
assertInputValueIsNot
判斷給定的輸入欄位不是給定值:
1$browser->assertInputValueIsNot($field, $value);
1$browser->assertInputValueIsNot($field, $value);
assertChecked
判斷給定多選況已勾選:
1$browser->assertChecked($field);
1$browser->assertChecked($field);
assertNotChecked
判斷給定多選況未勾選:
1$browser->assertNotChecked($field);
1$browser->assertNotChecked($field);
assertIndeterminate
判斷給定 Checkbox 是否為 Indeterminate 的狀態:
1$browser->assertIndeterminate($field);
1$browser->assertIndeterminate($field);
assertRadioSelected
判斷給定單選框欄位已選擇:
1$browser->assertRadioSelected($field, $value);
1$browser->assertRadioSelected($field, $value);
assertRadioNotSelected
判斷給定單選框欄位未選擇:
1$browser->assertRadioNotSelected($field, $value);
1$browser->assertRadioNotSelected($field, $value);
assertSelected
判斷給定下拉選單已選擇給定值:
1$browser->assertSelected($field, $value);
1$browser->assertSelected($field, $value);
assertNotSelected
判斷給定下拉選單未選擇給定值:
1$browser->assertNotSelected($field, $value);
1$browser->assertNotSelected($field, $value);
assertSelectHasOptions
判斷給定陣列中的值可被選取:
1$browser->assertSelectHasOptions($field, $values);
1$browser->assertSelectHasOptions($field, $values);
assertSelectMissingOptions
判斷給定陣列中的值不可被選取:
1$browser->assertSelectMissingOptions($field, $values);
1$browser->assertSelectMissingOptions($field, $values);
assertSelectHasOption
判斷給定值在給定欄位中可被選取:
1$browser->assertSelectHasOption($field, $value);
1$browser->assertSelectHasOption($field, $value);
assertSelectMissingOption
判斷給定值不可被選取:
1$browser->assertSelectMissingOption($field, $value);
1$browser->assertSelectMissingOption($field, $value);
assertValue
判斷符合給定選擇器的元素符合給定值:
1$browser->assertValue($selector, $value);
1$browser->assertValue($selector, $value);
assertValueIsNot
判斷符合給定選擇器的元素不符合給定值:
1$browser->assertValueIsNot($selector, $value);
1$browser->assertValueIsNot($selector, $value);
assertAttribute
判斷符合給定選擇器的元素中指定的屬性為給定值:
1$browser->assertAttribute($selector, $attribute, $value);
1$browser->assertAttribute($selector, $attribute, $value);
assertAttributeContains
判斷符合給定選擇器的元素中指定的屬性包含給定值:
1$browser->assertAttributeContains($selector, $attribute, $value);
1$browser->assertAttributeContains($selector, $attribute, $value);
assertAttributeDoesntContain
Assert that the element matching the given selector does not contain the given value in the provided attribute:
1$browser->assertAttributeDoesntContain($selector, $attribute, $value);
1$browser->assertAttributeDoesntContain($selector, $attribute, $value);
assertAriaAttribute
判斷符合給定選擇器的元素中指定的 Aria 屬性為給定值:
1$browser->assertAriaAttribute($selector, $attribute, $value);
1$browser->assertAriaAttribute($selector, $attribute, $value);
舉例來說,若有 <button aria-label="Add">
標記,則可像這樣判斷 aria-label
屬性:
1$browser->assertAriaAttribute('button', 'label', 'Add')
1$browser->assertAriaAttribute('button', 'label', 'Add')
assertDataAttribute
判斷符合給定選擇器的元素中指定的 Data 屬性為給定值:
1$browser->assertDataAttribute($selector, $attribute, $value);
1$browser->assertDataAttribute($selector, $attribute, $value);
舉例來說,若有 <tr id="row-1" data-content="attendees"></tr>
標記,則可像這樣判斷 data-label
屬性:
1$browser->assertDataAttribute('#row-1', 'content', 'attendees')
1$browser->assertDataAttribute('#row-1', 'content', 'attendees')
assertVisible
判斷符合給定選擇器的元素可見:
1$browser->assertVisible($selector);
1$browser->assertVisible($selector);
assertPresent
判斷符合給定選擇器的元素存在於原始碼中:
1$browser->assertPresent($selector);
1$browser->assertPresent($selector);
assertNotPresent
判斷符合給定選擇器的元素不存在於原始碼中:
1$browser->assertNotPresent($selector);
1$browser->assertNotPresent($selector);
assertMissing
判斷符合給定選擇器的元素不可見:
1$browser->assertMissing($selector);
1$browser->assertMissing($selector);
assertInputPresent
判斷給定名稱的輸入欄位存在:
1$browser->assertInputPresent($name);
1$browser->assertInputPresent($name);
assertInputMissing
判斷給定名稱的輸入欄位不存在:
1$browser->assertInputMissing($name);
1$browser->assertInputMissing($name);
assertDialogOpened
判斷有給定訊息的 JavaScript 對話方塊開啟:
1$browser->assertDialogOpened($message);
1$browser->assertDialogOpened($message);
assertEnabled
判斷給定欄位啟用:
1$browser->assertEnabled($field);
1$browser->assertEnabled($field);
assertDisabled
判斷給定欄位禁用:
1$browser->assertDisabled($field);
1$browser->assertDisabled($field);
assertButtonEnabled
判斷給定按鈕啟用:
1$browser->assertButtonEnabled($button);
1$browser->assertButtonEnabled($button);
assertButtonDisabled
判斷給定按鈕禁用:
1$browser->assertButtonDisabled($button);
1$browser->assertButtonDisabled($button);
assertFocused
判斷給定欄位已聚焦:
1$browser->assertFocused($field);
1$browser->assertFocused($field);
assertNotFocused
判斷給定欄位未聚焦:
1$browser->assertNotFocused($field);
1$browser->assertNotFocused($field);
assertAuthenticated
判斷使用者已登入:
1$browser->assertAuthenticated();
1$browser->assertAuthenticated();
assertGuest
判斷使用者未登入:
1$browser->assertGuest();
1$browser->assertGuest();
assertAuthenticatedAs
判斷使用者已登入為給定使用者:
1$browser->assertAuthenticatedAs($user);
1$browser->assertAuthenticatedAs($user);
assertVue
在 Dusk 中,甚至可以對 Vue 元件 資料的狀態進行 Assertion。舉例來說,假設網站中包含下列 Vue 元件:
1// HTML...23<profile dusk="profile-component"></profile>45// Component Definition...67Vue.component('profile', {8 template: '<div>{{ user.name }}</div>',910 data: function () {11 return {12 user: {13 name: 'Taylor'14 }15 };16 }17});
1// HTML...23<profile dusk="profile-component"></profile>45// Component Definition...67Vue.component('profile', {8 template: '<div>{{ user.name }}</div>',910 data: function () {11 return {12 user: {13 name: 'Taylor'14 }15 };16 }17});
則可像這樣判斷 Vue 元件的狀態:
1test('vue', function () {2 $this->browse(function (Browser $browser) {3 $browser->visit('/')4 ->assertVue('user.name', 'Taylor', '@profile-component');5 });6});
1test('vue', function () {2 $this->browse(function (Browser $browser) {3 $browser->visit('/')4 ->assertVue('user.name', 'Taylor', '@profile-component');5 });6});
1/**2 * A basic Vue test example.3 */4public function test_vue(): void5{6 $this->browse(function (Browser $browser) {7 $browser->visit('/')8 ->assertVue('user.name', 'Taylor', '@profile-component');9 });10}
1/**2 * A basic Vue test example.3 */4public function test_vue(): void5{6 $this->browse(function (Browser $browser) {7 $browser->visit('/')8 ->assertVue('user.name', 'Taylor', '@profile-component');9 });10}
assertVueIsNot
判斷給定的 Vue 元件資料屬性不符合給定值:
1$browser->assertVueIsNot($property, $value, $componentSelector = null);
1$browser->assertVueIsNot($property, $value, $componentSelector = null);
assertVueContains
判斷給定的 Vue 元件資料屬性為陣列,並包含給定值:
1$browser->assertVueContains($property, $value, $componentSelector = null);
1$browser->assertVueContains($property, $value, $componentSelector = null);
assertVueDoesntContain
判斷給定的 Vue 元件資料屬性為陣列,並且不包含給定值:
1$browser->assertVueDoesntContain($property, $value, $componentSelector = null);
1$browser->assertVueDoesntContain($property, $value, $componentSelector = null);
Page
有時候,測試可能會需要按照順序執行多個複雜的動作。這樣一來可能會使測試難以閱讀與理解。通過 Dusk Page,便可定義描述性的動作,並以單一方法來在給定頁面上執行。使用 Page 還可為網站或單一頁面上常用的選擇器定義捷徑。
產生 Page
若要產生 Page 物件,請執行 dusk:page
Artisan 指令。
1php artisan dusk:page Login
1php artisan dusk:page Login
設定 Page
預設情況下,Page 有三個方法:url
、assert
、與 elements
。我們現在先來討論 url
與 assert
方法。稍後會來詳細討論有關 elements
方法。
url
方法
url
方法應回傳代表該 Page 的 URL 之路徑。在瀏覽器中前往該頁面時,Dusk 會使用該 URL:
1/**2 * Get the URL for the page.3 */4public function url(): string5{6 return '/login';7}
1/**2 * Get the URL for the page.3 */4public function url(): string5{6 return '/login';7}
assert
方法
assert
方法可進行任意的 Assertion 判斷,來認證瀏覽器是否確實在該頁面上。該方法中不一定要有內容。不過,若有需要,可以自行進行 Assertion。這些 Assertion 會在前往該頁面後被自動執行:
1/**2 * Assert that the browser is on the page.3 */4public function assert(Browser $browser): void5{6 $browser->assertPathIs($this->url());7}
1/**2 * Assert that the browser is on the page.3 */4public function assert(Browser $browser): void5{6 $browser->assertPathIs($this->url());7}
Navigating to Pages
Page 建立好後,就可以使用 visit
方法來前往該 Page:
1use Tests\Browser\Pages\Login;23$browser->visit(new Login);
1use Tests\Browser\Pages\Login;23$browser->visit(new Login);
有時候,我們可能已經在某個給定頁面上,並且只需要將該頁面的選擇器與方法「載入」進目前的測試內容即可。常見的例子如通過點擊按鈕後跳轉至給定的頁面,而不是顯式前往該頁面。在此情況下,可使用 on
方法來載入頁面:
1use Tests\Browser\Pages\CreatePlaylist;23$browser->visit('/dashboard')4 ->clickLink('Create Playlist')5 ->on(new CreatePlaylist)6 ->assertSee('@create');
1use Tests\Browser\Pages\CreatePlaylist;23$browser->visit('/dashboard')4 ->clickLink('Create Playlist')5 ->on(new CreatePlaylist)6 ->assertSee('@create');
選擇器簡寫
Page 類別中的 elements
方法可讓你為頁面上的任意 CSS 選擇器定義快速、簡單好記的簡寫。舉例來說,來為網站登入頁的「email」輸入欄位定義捷徑:
1/**2 * Get the element shortcuts for the page.3 *4 * @return array<string, string>5 */6public function elements(): array7{8 return [9 '@email' => 'input[name=email]',10 ];11}
1/**2 * Get the element shortcuts for the page.3 *4 * @return array<string, string>5 */6public function elements(): array7{8 return [9 '@email' => 'input[name=email]',10 ];11}
定義好捷徑後,就可以在其他通常需要使用完整 CSS 選擇器的地方使用該選擇器簡寫:
全域選擇器簡寫
安裝好 Dusk 後,tests/Browser/Pages
目錄下會包含一個基礎的 Page
類別。該類別包含了一個 siteElements
方法,可用來定義在網站中所有頁面都可用的全域選擇器簡寫:
1/**2 * Get the global element shortcuts for the site.3 *4 * @return array<string, string>5 */6public static function siteElements(): array7{8 return [9 '@element' => '#selector',10 ];11}
1/**2 * Get the global element shortcuts for the site.3 *4 * @return array<string, string>5 */6public static function siteElements(): array7{8 return [9 '@element' => '#selector',10 ];11}
Page 方法
除了 Page 中預設定義的方法外,也可以定義額外的方法來在測試中使用。舉例來說,假設我們正在製作一個音樂管理軟體。在該軟體中,建立播放清單可能會是個常見的動作。比起在每個測試中重複撰寫建立播放清單的邏輯,我們可以在 Page 類別內定義 createPlaylist
方法:
1<?php23namespace Tests\Browser\Pages;45use Laravel\Dusk\Browser;6use Laravel\Dusk\Page;78class Dashboard extends Page9{10 // Other page methods...1112 /**13 * Create a new playlist.14 */15 public function createPlaylist(Browser $browser, string $name): void16 {17 $browser->type('name', $name)18 ->check('share')19 ->press('Create Playlist');20 }21}
1<?php23namespace Tests\Browser\Pages;45use Laravel\Dusk\Browser;6use Laravel\Dusk\Page;78class Dashboard extends Page9{10 // Other page methods...1112 /**13 * Create a new playlist.14 */15 public function createPlaylist(Browser $browser, string $name): void16 {17 $browser->type('name', $name)18 ->check('share')19 ->press('Create Playlist');20 }21}
定義好該方法後,就可以在任何使用該 Page 的測試中使用該方法。Browser 實體會自動作為第一個引數傳入給自訂 Page 方法內:
1use Tests\Browser\Pages\Dashboard;23$browser->visit(new Dashboard)4 ->createPlaylist('My Playlist')5 ->assertSee('My Playlist');
1use Tests\Browser\Pages\Dashboard;23$browser->visit(new Dashboard)4 ->createPlaylist('My Playlist')5 ->assertSee('My Playlist');
元件
Component (元件) 與 Dusk 的「Page 物件」類似,不同的地方在於元件是用於一小部分的 UI,且在整個網站中都可重複使用。如:導航列或通知視窗。因此,元件並不限定於特定的 URL。
產生 Component
若要產生 Component,請執行 dusk:component
Artisan 指令。新建立的 Component 會放置於 tests/Browser/Components
目錄中:
1php artisan dusk:component DatePicker
1php artisan dusk:component DatePicker
像上面這樣,「date picker」是一個範例元件,該元件可能會在網站的各種頁面上出現。若要在測試套件中的數十個測試內手動轉寫瀏覽器自動化邏輯會很麻煩。因此,我們可以改用 Dusk Component 來代表 Date Picker,進而將此一邏輯封裝在該元件內:
1<?php23namespace Tests\Browser\Components;45use Laravel\Dusk\Browser;6use Laravel\Dusk\Component as BaseComponent;78class DatePicker extends BaseComponent9{10 /**11 * Get the root selector for the component.12 */13 public function selector(): string14 {15 return '.date-picker';16 }1718 /**19 * Assert that the browser page contains the component.20 */21 public function assert(Browser $browser): void22 {23 $browser->assertVisible($this->selector());24 }2526 /**27 * Get the element shortcuts for the component.28 *29 * @return array<string, string>30 */31 public function elements(): array32 {33 return [34 '@date-field' => 'input.datepicker-input',35 '@year-list' => 'div > div.datepicker-years',36 '@month-list' => 'div > div.datepicker-months',37 '@day-list' => 'div > div.datepicker-days',38 ];39 }4041 /**42 * Select the given date.43 */44 public function selectDate(Browser $browser, int $year, int $month, int $day): void45 {46 $browser->click('@date-field')47 ->within('@year-list', function (Browser $browser) use ($year) {48 $browser->click($year);49 })50 ->within('@month-list', function (Browser $browser) use ($month) {51 $browser->click($month);52 })53 ->within('@day-list', function (Browser $browser) use ($day) {54 $browser->click($day);55 });56 }57}
1<?php23namespace Tests\Browser\Components;45use Laravel\Dusk\Browser;6use Laravel\Dusk\Component as BaseComponent;78class DatePicker extends BaseComponent9{10 /**11 * Get the root selector for the component.12 */13 public function selector(): string14 {15 return '.date-picker';16 }1718 /**19 * Assert that the browser page contains the component.20 */21 public function assert(Browser $browser): void22 {23 $browser->assertVisible($this->selector());24 }2526 /**27 * Get the element shortcuts for the component.28 *29 * @return array<string, string>30 */31 public function elements(): array32 {33 return [34 '@date-field' => 'input.datepicker-input',35 '@year-list' => 'div > div.datepicker-years',36 '@month-list' => 'div > div.datepicker-months',37 '@day-list' => 'div > div.datepicker-days',38 ];39 }4041 /**42 * Select the given date.43 */44 public function selectDate(Browser $browser, int $year, int $month, int $day): void45 {46 $browser->click('@date-field')47 ->within('@year-list', function (Browser $browser) use ($year) {48 $browser->click($year);49 })50 ->within('@month-list', function (Browser $browser) use ($month) {51 $browser->click($month);52 })53 ->within('@day-list', function (Browser $browser) use ($day) {54 $browser->click($day);55 });56 }57}
使用 Component
定義好 Component 後,便可輕鬆地在任何測試內於 Date Picker 中選擇日期。而且,若選擇日期所需要的邏輯更改了,我們只需要更新 Component 即可:
1<?php23use Illuminate\Foundation\Testing\DatabaseMigrations;4use Laravel\Dusk\Browser;5use Tests\Browser\Components\DatePicker;67uses(DatabaseMigrations::class);89test('basic example', function () {10 $this->browse(function (Browser $browser) {11 $browser->visit('/')12 ->within(new DatePicker, function (Browser $browser) {13 $browser->selectDate(2019, 1, 30);14 })15 ->assertSee('January');16 });17});
1<?php23use Illuminate\Foundation\Testing\DatabaseMigrations;4use Laravel\Dusk\Browser;5use Tests\Browser\Components\DatePicker;67uses(DatabaseMigrations::class);89test('basic example', function () {10 $this->browse(function (Browser $browser) {11 $browser->visit('/')12 ->within(new DatePicker, function (Browser $browser) {13 $browser->selectDate(2019, 1, 30);14 })15 ->assertSee('January');16 });17});
1<?php23namespace Tests\Browser;45use Illuminate\Foundation\Testing\DatabaseMigrations;6use Laravel\Dusk\Browser;7use Tests\Browser\Components\DatePicker;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 /**13 * A basic component test example.14 */15 public function test_basic_example(): void16 {17 $this->browse(function (Browser $browser) {18 $browser->visit('/')19 ->within(new DatePicker, function (Browser $browser) {20 $browser->selectDate(2019, 1, 30);21 })22 ->assertSee('January');23 });24 }25}
1<?php23namespace Tests\Browser;45use Illuminate\Foundation\Testing\DatabaseMigrations;6use Laravel\Dusk\Browser;7use Tests\Browser\Components\DatePicker;8use Tests\DuskTestCase;910class ExampleTest extends DuskTestCase11{12 /**13 * A basic component test example.14 */15 public function test_basic_example(): void16 {17 $this->browse(function (Browser $browser) {18 $browser->visit('/')19 ->within(new DatePicker, function (Browser $browser) {20 $browser->selectDate(2019, 1, 30);21 })22 ->assertSee('January');23 });24 }25}
持續整合 (CI, Continuous Integration)
大多數的 Dusk CI 設定都假設你的 Laravel 應用程式放在連接埠 8000 的 PHP 內建開發伺服器上。因此,在繼續前,請先確保 CI 環境上有將 APP_URL
環境變數設為 http://127.0.0.1:8000
。
Heroku CI
若要在 Heroku CI 上執行 Dusk 測試,請將下列 Google Chrome Buildpack 與指令嗎加到 Heroku 的 app.json
檔中:
1{2 "environments": {3 "test": {4 "buildpacks": [5 { "url": "heroku/php" },6 { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }7 ],8 "scripts": {9 "test-setup": "cp .env.testing .env",10 "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"11 }12 }13 }14}
1{2 "environments": {3 "test": {4 "buildpacks": [5 { "url": "heroku/php" },6 { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }7 ],8 "scripts": {9 "test-setup": "cp .env.testing .env",10 "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"11 }12 }13 }14}
Travis CI
若要在 Travis CI 上執行 Dusk 測試,請使用下列 .travis.yml
設定檔。由於 Travis CI 並非圖形化環境,因此若要啟動 Chrome 瀏覽器,我們需要做一些額外的步驟。此外,我們會使用 php artisan serve
來啟動 PHP 的內建網頁伺服器:
1language: php23php:4 - 8.256addons:7 chrome: stable89install:10 - cp .env.testing .env11 - travis_retry composer install --no-interaction --prefer-dist12 - php artisan key:generate13 - php artisan dusk:chrome-driver1415before_script:16 - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &17 - php artisan serve --no-reload &1819script:20 - php artisan dusk
1language: php23php:4 - 8.256addons:7 chrome: stable89install:10 - cp .env.testing .env11 - travis_retry composer install --no-interaction --prefer-dist12 - php artisan key:generate13 - php artisan dusk:chrome-driver1415before_script:16 - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &17 - php artisan serve --no-reload &1819script:20 - php artisan dusk
GitHub Actions
若要使用 GitHub Actions 來執行 Dusk 測試,可參考下列設定檔。與 TravisCI 一樣,我們會使用 php artisan serve
指令來啟動 PHP 的內建網頁伺服器:
1name: CI2on: [push]3jobs:45 dusk-php:6 runs-on: ubuntu-latest7 env:8 APP_URL: "http://127.0.0.1:8000"9 DB_USERNAME: root10 DB_PASSWORD: root11 MAIL_MAILER: log12 steps:13 - uses: actions/checkout@v414 - name: Prepare The Environment15 run: cp .env.example .env16 - name: Create Database17 run: |18 sudo systemctl start mysql19 mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"20 - name: Install Composer Dependencies21 run: composer install --no-progress --prefer-dist --optimize-autoloader22 - name: Generate Application Key23 run: php artisan key:generate24 - name: Upgrade Chrome Driver25 run: php artisan dusk:chrome-driver --detect26 - name: Start Chrome Driver27 run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &28 - name: Run Laravel Server29 run: php artisan serve --no-reload &30 - name: Run Dusk Tests31 run: php artisan dusk32 - name: Upload Screenshots33 if: failure()34 uses: actions/upload-artifact@v435 with:36 name: screenshots37 path: tests/Browser/screenshots38 - name: Upload Console Logs39 if: failure()40 uses: actions/upload-artifact@v441 with:42 name: console43 path: tests/Browser/console
1name: CI2on: [push]3jobs:45 dusk-php:6 runs-on: ubuntu-latest7 env:8 APP_URL: "http://127.0.0.1:8000"9 DB_USERNAME: root10 DB_PASSWORD: root11 MAIL_MAILER: log12 steps:13 - uses: actions/checkout@v414 - name: Prepare The Environment15 run: cp .env.example .env16 - name: Create Database17 run: |18 sudo systemctl start mysql19 mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"20 - name: Install Composer Dependencies21 run: composer install --no-progress --prefer-dist --optimize-autoloader22 - name: Generate Application Key23 run: php artisan key:generate24 - name: Upgrade Chrome Driver25 run: php artisan dusk:chrome-driver --detect26 - name: Start Chrome Driver27 run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &28 - name: Run Laravel Server29 run: php artisan serve --no-reload &30 - name: Run Dusk Tests31 run: php artisan dusk32 - name: Upload Screenshots33 if: failure()34 uses: actions/upload-artifact@v435 with:36 name: screenshots37 path: tests/Browser/screenshots38 - name: Upload Console Logs39 if: failure()40 uses: actions/upload-artifact@v441 with:42 name: console43 path: tests/Browser/console
Chipper CI
若使用 Chipper CI 來執行 Dusk 測試,則可使用下列設定檔作為起始點。我們會使用 PHP 的內建伺服器來執行 Laravel 以監聽 Request:
1# file .chipperci.yml2version: 134environment:5 php: 8.26 node: 1678# Include Chrome in the build environment9services:10 - dusk1112# Build all commits13on:14 push:15 branches: .*1617pipeline:18 - name: Setup19 cmd: |20 cp -v .env.example .env21 composer install --no-interaction --prefer-dist --optimize-autoloader22 php artisan key:generate2324 # Create a dusk env file, ensuring APP_URL uses BUILD_HOST25 cp -v .env .env.dusk.ci26 sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci2728 - name: Compile Assets29 cmd: |30 npm ci --no-audit31 npm run build3233 - name: Browser Tests34 cmd: |35 php -S [::0]:8000 -t public 2>server.log &36 sleep 237 php artisan dusk:chrome-driver $CHROME_DRIVER38 php artisan dusk --env=ci
1# file .chipperci.yml2version: 134environment:5 php: 8.26 node: 1678# Include Chrome in the build environment9services:10 - dusk1112# Build all commits13on:14 push:15 branches: .*1617pipeline:18 - name: Setup19 cmd: |20 cp -v .env.example .env21 composer install --no-interaction --prefer-dist --optimize-autoloader22 php artisan key:generate2324 # Create a dusk env file, ensuring APP_URL uses BUILD_HOST25 cp -v .env .env.dusk.ci26 sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci2728 - name: Compile Assets29 cmd: |30 npm ci --no-audit31 npm run build3233 - name: Browser Tests34 cmd: |35 php -S [::0]:8000 -t public 2>server.log &36 sleep 237 php artisan dusk:chrome-driver $CHROME_DRIVER38 php artisan dusk --env=ci
若要瞭解更多有關在 Chipper CI 上執行 Dusk 測試的資訊,包含如何使用資料庫等,請參考官方的 Chipper CI 說明文件。