javck
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: laravel,單元測試 --- # 單元測試 - 瀏覽器測試 ### 簡介 Laravel Dusk 提供了直覺、簡單易用的瀏覽器自動化及測試 API 。預設情況下,Dusk 不需要在你的機器上安裝 JDK 或者 Selenium 。而是需要使用單獨的 ChromeDriver 進行安裝。當然,你也可以自由使用其他的兼容 Selenium 的驅動 ### 安裝 首先,利用 Composer 在項目中添加 laravel/dusk 這個依賴套件 : `composer require --dev laravel/dusk` > 注意: > > 如果你是手動註冊 Dusk 服務供應器,一定不要在你的正式環境中註冊,這樣可能會導致一些不守規矩的用戶擁有控制你應用的權限 在安裝好 Dusk 套件後,執行 dusk:install 命令。這個命令將會建立一個 tests/Browser 資料夾以及一個 Dusk 示範測試用例: `php artisan dusk:install` 接下來,在 .env 檔案中設置 APP_URL 參數。這個值應該與你在瀏覽器中打開應用的網址首頁 URL 相同 假如你是使用 Laravel Sail 來管理你本地開發環境,請別忘了參考 Sail 官方文件來設定並執行 Dsk 測試 #### 管理 ChromeDriver 安裝 如果你想安裝與 Laravel Dusk 附帶版本不同的 ChromeDriver,可以使用 dusk:chrome-driver 命令: ##### 安裝你所屬作業系統的最新版本 ChromeDriver `php artisan dusk:chrome-driver` ##### 安裝你所屬作業系統的指定版本 ChromeDriver `php artisan dusk:chrome-driver 86` ##### 為所有支持的作業系統安裝指定版本的 ChromeDriver... `php artisan dusk:chrome-driver --all` ##### 安裝符合你作業系統的 Chrome/Chromium 版本的 ChromeDriver `php artisan dusk:chrome-driver --detect` >注意: >Dusk 要求 ChromeDriver 的二進位文件 (binaries) 是可執行的。如果在 Dusk 運行時遇到問題,可以使用以下命令來調整其權限,以確保二進位文件 (binaries) 是可執行的 `chmod -R 0755 vendor/laravel/dusk/bin` #### 使用其他瀏覽器 預設情況下, Dusk 使用 Google Chrome 瀏覽器和一個單獨安裝的 ChromeDriver 來運行你的瀏覽器測試。當然,你可以運行自己的 Selenium 服務來用任何你想用的瀏覽器來進行測試 如果要這麼做,打開 tests/DuskTestCase.php 文件,這個是應用測試用例的父類別。在這個文件中,移除對 startChromeDriver() 的呼叫。這樣 Dusk 就不會自動啟動 ChromeDriver 了 ```php //tests\DuskTestCase.php //準備執行 Dusk 測試 public static function prepare() { // static::startChromeDriver(); } ``` 然後,你可以修改 driver() 來連接到你選定的 URL 和端口。此外,你可以修改 「desired capabilities」(期望能力),它將會被傳遞給 WebDriver ```php //tests\DuskTestCase.php //創建 RemoteWebDriver 實例 protected function driver() { return RemoteWebDriver::create( 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() ); } ``` ### 快速開始 #### 創建測試 要創建一個 Dusk 測試,可以使用 dusk:make 命令。創建的測試將會被放在 tests/Browser 目錄中: `php artisan dusk:make LoginTest` #### 資料庫遷移 大部分你寫的測試將會和從資料庫取值的頁面來進行交互;然而你的 Dusk 測試應永遠都不使用 RefreshDatabase 這個 Trait。該 Trait 利用資料庫的 Transaction 機制在 HTTP 請求時是不適用的。替代方案是使用 DatabaseMigration 這個 Trait。它將會在每一次的測試時重新執行資料庫遷移 ```php //tests\Browser\ExampleTest.php namespace Tests\Browser; use App\Models\User; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Chrome; use Tests\DuskTestCase; class ExampleTest extends DuskTestCase { use DatabaseMigrations; } ``` 另外,在執行 Dusk 測試時也不能夠使用 SQLite in-memory 資料庫。這是因為瀏覽器執行在它自己的線程因而無法取用位在其它線程的 in-memory 資料庫 #### 執行測試 如要運作你的瀏覽器測試,請執行 dusk 命令,如下: `php artisan dusk` 如果上次運行 dusk 命令時測試失敗,則可以通過使用 dusk:fails 命令重新運行失敗的測試以節省時間 `php artisan dusk:fails` dusk 命令接受任何能用於 PHPUnit 的參數。例如,讓你可以在指定 group 中運行測試 `php artisan dusk --group=foo` ##### 手動開啟 ChromeDriver 預設情況下,Dusk 會嘗試自動運行 ChromeDriver。如果你在特定的系統中不能運行,可以在運行 dusk 命令前通過手動的方式來運行 ChromeDriver。 如果你選擇手動運行 ChromeDriver,你需要在你的 `tests/DuskTestCase.php` 文件中註解掉下面這一行: ```php //tests\DuckTestCase.php //為 Dusk 測試做準備 public static function prepare() { // static::startChromeDriver(); } ``` 此外如果你的 ChromeDriver 並非運行在端口 9515,你需要修改同一個類別的 driver() ,修改成正確的端口號: ```php //tests\DuckTestCase.php //創建 RemoteWebDriver 實例 protected function driver() { return RemoteWebDriver::create( 'http://localhost:9515', DesiredCapabilities::chrome() ); } ``` #### 環境處理 為了讓 Dusk 使用自己的環境設定來運行測試,你需要在專案根目錄創建一個名為 .env.dusk.{environment} 的文件。例如,你想用 local 環境來運行 dusk 命令就需要創建一個 .env.dusk.local 文件 運行測試的時候,Dusk 會備份你的 .env 文件並且重命名你的 Dusk 環境文件為 .env。當測試結束後,它會再還原你的 .env 文件內容 #### 創建瀏覽器 首先讓我們來寫一個測試用例,這個例子用來驗證能夠使用登入系統。生成測試用例之後,我們可以對它稍作調整並讓它可以跳轉到登入界面,輸入登入帳密之後,點擊「登入」按鈕。我們通過在測試用例中呼叫 browse() 來創建一個瀏覽器實例: ```php //tests\Browser\ExampleTest.php namespace Tests\Browser; use App\Models\User; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Chrome; use Tests\DuskTestCase; class ExampleTest extends DuskTestCase { use DatabaseMigrations; public function test_basic_example() { $user = User::factory()->create([ 'email' => 'info@goblinlab.org', ]); $this->browse(function ($browser) use ($user) { $browser->visit('/login') ->type('email', $user->email) ->type('password', 'password') ->press('Login') ->assertPathIs('/home'); }); } } ``` 如上例所示,browse() 接收了一個 Closure 作為參數。Dusk 會自動將這個瀏覽器實例注入到回呼過程中,而且這個瀏覽器實例可以和你的應用進行交互並進行各種確認 #### 創建多個瀏覽器 有時候你可能需要多個瀏覽器才能正確的進行測試。例如,使用多個瀏覽器測試通過 websockets 進行通訊的在線聊天頁面。想要創建多個瀏覽器,需要在 browse() 的回呼中,用更多的參數作為名字來區分瀏覽器實例,然後傳給回呼去加入多個瀏覽器實例,看下面這個例子會比較清楚: ```php $this->browse(function ($first, $second) { $first->loginAs(User::find(1)) ->visit('/home') ->waitForText('Message'); $second->loginAs(User::find(2)) ->visit('/home') ->waitForText('Message') ->type('message', 'Hey Taylor') ->press('Send'); $first->waitForText('Hey Zack') ->assertSee('Goblin Lab Studio'); }); ``` #### 網址切換 visit() 能用來將網址切換到某個指定的 URI: `$browser->visit('/login');` 如果你有為路由取名字的習慣,也能夠使用 visitRoute() 來切換到指定的命名路由: `$browser->visitRoute('login');` 你也能夠回到上一頁或者是到下一頁,透過 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) 如果你想定義一個可以在各種測試中重複使用的自定義瀏覽器方法,可以呼叫 Browser 類別的 macro() 。通常情況下,你會從服務提供者的 boot() 中去呼叫它: ```php //app\Providers\DuskServiceProvider.php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Laravel\Dusk\Browser; class DuskServiceProvider extends ServiceProvider { //註冊Dusk的瀏覽器宏 public function boot() { Browser::macro('scrollToElement', function ($element = null) { $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);"); return $this; }); } } ``` macro() 接受一個方法名作為其第一個參數,並接受 Closure 作為其第二個參數。當對著瀏覽器實例呼叫與宏同名的方法時,將會執行宏的Closure 下面這個例子所呼叫的 scrollToElement() 就是我們自定義的瀏覽器宏 ```php $this->browse(function ($browser) use ($user) { $browser->visit('/pay') ->scrollToElement('#credit-card-details') ->assertSee('Enter Credit Card Details'); }); ``` #### 用戶認證 我們經常會測試需要身份驗證過後才能進入的頁面,你可以使用 Dusk 的 loginAs() 來避免在每次測試期間與登錄頁面進行交互。 loginAs() 接受用戶 ID 或者是用戶模型實例: ```php use App\Models\User; $this->browse(function ($browser) { $browser->loginAs(User::find(1)) ->visit('/home'); }); ``` >注意: > >使用 loginAs() 後,用戶會話在該文件中的所有測試被維護,也就不需再次進行登入 #### Cookies 你可以使用 cookie() 來獲取或者設置加密過的 cookie 的值。預設情況下,所有被 Laravel 所建立的 cookie 都會被加密 ```php //獲取加密 cookie $browser->cookie('name'); //設置加密 cookie $browser->cookie('name', 'Zack'); ``` 使用 plainCookie() 則可以獲取或者設置未加密過的 cookie 的值 ```php //獲取未加密 cookie $browser->plainCookie('name'); //設置未加密 cookie $browser->plainCookie('name', 'Zack'); ``` 你可以使用 deleteCookie() 刪除指定的 cookie ```php $browser->deleteCookie('name'); ``` #### 執行 JavaScript 你能夠使用 script() 以便於在瀏覽器內去執行 JavaScript 腳本 ```php //單行腳本 $output = $browser->script('document.documentElement.scrollTop = 0'); //多行腳本 $output = $browser->script([ 'document.body.scrollTop = 0', 'document.documentElement.scrollTop = 0', ]); ``` #### 獲取截圖 你可以使用 screenshot() 來截圖並將其以指定文件名來進行儲存,所有截圖都將存放在 tests/Browser/screenshots 目錄之下 ```php $browser->screenshot('filename'); ``` #### 將控制台輸出結果保存到硬碟內 你可以使用 storeConsoleLog() 將控制台內容輸出到指定文件名的檔案並寫入硬碟內,控制台輸出預設存放在 tests/Browser/console 資料夾內 ```php $browser->storeConsoleLog('filename'); ``` #### 將頁面的源碼保存到硬碟內 你可以使用 storeSource() 將頁面當前源代碼輸出到指定文件名的檔案並寫入硬碟內,頁面源代碼預設會存放到 tests/Browser/source 資料夾內 ```php $browser->storeSource('filename'); ``` #### 與元素交互 ##### Dusk 選擇器 編寫 Dusk 測試最困難的部分之一就是選擇良好的 CSS 選擇器以便與元素進行交互。 隨着時間的推移,前端框架的更改可能會導致如下所示的 CSS 選擇器無法通過測試: ``` // HTML結構... <button>Login</button> // 測試用例... $browser->click('.login-page .container div > button'); ``` 為解決這個問題,Dusk 選擇器可以讓你專注於編寫有效的測試,而不必記住 CSS 選擇器。要定義一個選擇器,你需要添加一個 dusk 屬性在 HTML 元素中。然後在選擇器前面加上 @ 用來在 Dusk 測試中操作該元素,請看上面這個例子的進化版本 ``` // HTML結構... <button dusk="login-button">Login</button> // 測試用例... $browser->click('@login-button'); ``` #### 文本(Text)、值(Value) & 屬性(Attributes) ##### 獲取 & 設置值 Dusk 提供了多個方法用於和頁面元素的當前顯示文本、值和屬性進行交互,例如,要獲取對應指定選擇器的元素的「值」可以使用 value() ```php // 獲取值... $value = $browser->value('selector'); // 設置值... $browser->value('selector', 'value'); // 獲取輸入元素的值... $value = $browser->inputValue('field'); ``` ##### 獲取文本 text() 可以用於獲取對應指定選擇器元素的文本 ```php $text = $browser->text('selector'); ``` ##### 獲取屬性 最後,attribute() 可以用於獲取對應指定選擇器元素的屬性 ```php $attribute = $browser->attribute('selector', 'value'); ``` #### 使用表單 ##### 輸入值 Dusk 提供了多種方法來與表單和輸入元素進行交互。首先,讓我們看一個在輸入項中輸入值的範例: ```php $browser->type('email', 'info@goblinlab.org'); ``` > 注意 > > 儘管該方法在需要時可以傳入,但其實我們不需要將 CSS 選擇器傳遞給 type() 。如果沒有提供 CSS 選擇器的話,Dusk 會搜索包含指定 name 屬性的輸入項,最後 Dusk 還會嘗試尋找包含指定 name 屬性的 textarea輸入項 要想將文本附加到一個輸入項之後而不清除其內容, 你可以使用 append() : ```php $browser->type('tags', 'foo') ->append('tags', ', bar, baz'); ``` 你可以使用 clear() 來清除輸入值 ```php $browser->clear('email'); ``` 你可以使用 typeSlowly() 去指示 Dusk 緩慢的輸入。預設情況下,Dusk 在兩次按鍵之間將暫停100毫秒。要自定義按鍵之間的時間量,你可以將適當的毫秒數作為方法的第二個參數傳入 ```php $browser->typeSlowly('tel', '(02)82752408'); $browser->typeSlowly('tel', '(02)82752408', 300); ``` 你還可以使用 appendSlowly() 來緩慢添加文本 ```php $browser->type('tags', 'foo') ->appendSlowly('tags', ', bar, baz'); ``` #### 下拉選單 需要在下拉選單中選擇值,你可以使用 select() 。 類似於 type() , select() 並不是一定要傳入 CSS 選擇器。 當使用 select() 時,你應該傳遞選項實際的值而不是它的顯示文字: ```php $browser->select('size', 'Large'); ``` 你也可以通過省略第二個參數來隨機選擇一個選項 ```php $browser->select('size'); ``` #### 複選框 使用「check」 複選框時,你可以使用 check() 。 像其他許多與輸入項相關的方法,並不是必須傳入 CSS 選擇器。 如果準確的選擇器無法找到的時候,Dusk 會搜索能夠與 name 屬性對應的複選框: ```php $browser->check('terms'); ``` uncheck()用來取消複選框的勾選 ```php $browser->uncheck('terms'); ``` #### 單選按鈕 使用 「select」中單選按鈕選項時,你可以使用 radio() 。 像很多其他的與輸入項相關的方法一樣, 它也並不是必須傳入 CSS 選擇器。如果準確的選擇器無法被找到的時候, Dusk 會搜索能夠與 name 屬性或者 value 屬性相對應的單選按鈕 ```php $browser->radio('size', 'large'); ``` #### 附件 attach() 可以附加一個文件到 file input 元素中。 像很多其他的與輸入項相關的方法一樣,他也並不是必須傳入 CSS 選擇器。如果準確的選擇器沒有被找到的時候, Dusk 會搜索與 name 屬性對應的檔案輸入框 $browser->attach('photo', __DIR__.'/photos/mountains.png'); >注意: > >attach() 需要使用 PHP Zip 擴展,因此你的伺服器必須安裝此擴展 #### 按下按鈕 press() 能夠用來點擊頁面上的按鈕元素。方法的第一參數可以是按鈕上的文字,也可以是 CSS 或 Dusk 選擇器 ```php $browser->press('Login'); ``` 當提交表單後,大多數應用會在表單的提交按鈕被按下後失效,直到表單的提交請求結束後才會重新啟用。要按下按鈕並等待該按鈕重新啟用,可以使用 pressAndWaitFor() ```php //按下按鈕並等待按鈕重新啟用,最多等待5秒 $browser->pressAndWaitFor('Save'); //按下按鈕並等待按鈕重新啟用,最多等待1秒 $browser->pressAndWaitFor('Save', 1); ``` #### 點擊超連結 要點擊超連結,可以在瀏覽器實例下使用 clickLink() 。 clickLink() 將點擊指定文字的超連結: ```php $browser->clickLink($linkText); ``` 你可以使用 seeLink() 來確定具有指定顯示文字的超連結在頁面上是否可見: ```php if ($browser->seeLink($linkText)) { // ... } ``` >注意: > >這些方法與 jQuery 交互。 如果頁面上沒有 jQuery , Dusk 會自動將其注入到頁面中,以便在測試期間使用 #### 使用鍵盤 keys() 讓你可以在指定元素中輸入比 type() 更加複雜的輸入序列。例如,你可以在輸入值的同時按下按鍵。在這個例子中,輸入 goblin 時, shift 鍵也同時被按下。當 goblin 輸入完之後, 將會輸入 lab 而不會按下任何組合按鍵: ```php $browser->keys('selector', ['{shift}', 'goblin'], 'lab'); ``` 另一個有用的 keys() 用法,是寄送快捷鍵組合給你應用的主要 CSS 選擇器 ```php $browser->keys('.app', ['{command}', 'j']); ``` 所有包在 {} 中的鍵盤按鍵,都對應定義於 Facebook\WebDriver\WebDriverKeys 類別中,你可以在[這裏](https://php-webdriver.github.io/php-webdriver/1.0.3/Facebook/WebDriver/WebDriverKeys.html)找到。 #### 使用滑鼠 ##### 點擊元素 click() 方法可用於「點擊」與給定選擇器對應的元素: ```php $browser->click('.selector'); ``` clickAtXPath() 方法可用於「單擊」與指定 XPath 表達式對應的元素 ```php $browser->clickAtXPath('//div[@class = "selector"]'); ``` clickAtPoint() 可用於「點擊」相對於瀏覽器可視區域的指定坐標對上的最高元素 ```php $browser->clickAtPoint($x = 0, $y = 0); ``` doubleClick() 可用於模擬滑鼠的雙擊 ```php $browser->doubleClick(); ``` rightClick() 可用於模擬滑鼠的右擊: ```php $browser->rightClick(); $browser->rightClick('.selector'); ``` clickAndHold() 可用於模擬被單擊並按住的滑鼠按鈕。 隨後呼叫 releaseMouse() 將取消此行為並放開滑鼠按鈕 ```php $browser->clickAndHold() ->pause(1000) ->releaseMouse(); ``` ##### 滑鼠懸停 mouseover() 可用於與指定選擇器對應的元素的滑鼠懸停動作: ```php $browser->mouseover('.selector'); ``` ##### 拖拉操作 drag() 用於將與指定選擇器對應的元素拖到其它元素: ```php $browser->drag('.from-selector', '.to-selector'); ``` 或者,你也可以在單一方向上拖動元素: ```php $browser->dragLeft('.selector', $pixels = 10); $browser->dragRight('.selector', $pixels = 10); $browser->dragUp('.selector', $pixels = 10); $browser->dragDown('.selector', $pixels = 10); ``` 最後,你可以將元素拖動指定的偏移量 ```php $browser->dragOffset('.selector', $x = 10, $y = 10); ``` #### JavaScript 對話框 Dusk 提供了幾種與 JavaScript 對話框交互的方法。比如,你能夠使用 waitForDialog() 去等待 JavaScript 對話框出現,這個方法接受一個可選參數來設定要等待幾秒鐘來等對話框出現 ```php // 等待對話框顯示: $browser->waitForDialog($seconds = null); ``` ```php // assertDialogOpened() 用來確認對話框已經顯示,並且上面顯示的訊息與指定值相同 $browser->assertDialogOpened('Dialog message'); ``` 假如 JavaScript 對話框包含輸入項,你能夠使用 typeInDialog() 來輸入內容: ```php $browser->typeInDialog('Hello World'); ``` 為了要透過按下 "OK" 按鈕來關閉一個打開的 JavaScript 對話框,你能夠呼叫 acceptDialog() ```php $browser->acceptDialog(); ``` 為了要透過按下 "Cancel" 按鈕來關閉一個打開的 JavaScript 對話框,你能夠呼叫 dismissDialog() ```php $browser->dismissDialog(); ``` #### 選擇器作用範圍 有時可能希望在指定的選擇器範圍內執行多個操作。比如,可能想要確認表格中存在某些文字,然後點擊表格中的一個按鈕。可以使用 with() 實現此需求。回呼函數內所有被執行的操作都被限定在原始的選擇器上: ```php $browser->with('.table', function ($table) { $table->assertSee('Hello World') ->clickLink('Delete'); }); ``` 你可能偶爾需要在當前範圍之外執行確認。 你可以使用 elsewhere() 來完成此操作: ```php $browser->with('.table', function ($table) { // Current scope is `body .table`... $browser->elsewhere('.page-title', function ($title) { // Current scope is `body .page-title`... $title->assertSee('Hello World'); }); $browser->elsewhereWhenAvailable('.page-title', function ($title) { // Current scope is `body .page-title`... $title->assertSee('Hello World'); }); }); ``` #### 等待元素 在測試大量使用 JavaScript 的應用時,在進行測試之前,經常需要「等待」指定元素或資料可用。Dusk 讓這件事情變得更容易。使用一系列方法,可以等到頁面元素可用,甚至指定的 JavaScript 表達式執行結果為 true ##### 等待 如果需要測試暫停指定的毫秒數,可以使用 pause() : ##### 等待選擇器 waitFor() 可以用於暫停執行測試,直到頁面上與給定 CSS 選擇器對應的元素被顯示。預設情況下,將在暫停超過 5 秒後拋出異常。如果有必要,可以傳入自定義超時時長作為其第二個參數: ```php // 等待選擇器 5 秒時間... $browser->waitFor('.selector'); // 等待選擇器 1 秒時間... $browser->waitFor('.selector', 1); ``` 你也能夠等到直至對應選擇器包含指定的文字 ```php // 等待選擇器最多 5 秒的時間,直到選擇器對應元素包含指定文字... $browser->waitForTextIn('.selector', 'Hello World'); // 等待選擇器最多 1 秒的時間,直到選擇器對應元素包含指定文字... $browser->waitForTextIn('.selector', 'Hello World', 1); // 等待選擇器直到選擇器對應元素消失... $browser->waitUntilMissing('.selector'); // 等待選擇器最多 1 秒的時間,直到選擇器對應元素消失... $browser->waitUntilMissing('.selector', 1); ``` #### 選擇器可用時,限定作用域範圍 有時,你或許希望等待指定選擇器,然後與對應選擇器的元素進行交互。例如,你可能希望等到 "modal" 視窗可用,然後在 "modal" 視窗中點擊「確定」按鈕。在這種情況下,可以使用 whenAvailable() 。指定回呼內的所有要執行的元素操作都將被限定在起始選擇器上: ```php $browser->whenAvailable('.modal', function ($modal) { $modal->assertSee('Hello World') ->press('OK'); }); ``` ##### 等待文字 waitForText() 可以用於等待頁面上指定文字被顯示在頁面上 ```php //等待頁面上出現指定文字最多 5 秒時間... $browser->waitForText('Hello World'); //等待頁面上出現指定文字最多 1 秒時間... $browser->waitForText('Hello World', 1); ``` 你也能夠使用 waitUntilMissingText() 去等待直到指定文字消失於頁面上 ```php //等待頁面上指定文字消失,最多 5 秒時間... $browser->waitUntilMissingText('Hello World'); //等待頁面上指定文字消失,最多 1 秒時間... $browser->waitUntilMissingText('Hello World', 1); ``` ##### 等待超連結 waitForLink() 用於等待指定超連結文字在頁面上顯示 //等待頁面上出現指定文字的超連結最多 5 秒時間... $browser->waitForLink('Create'); //等待頁面上出現指定文字的超連結最多 1 秒時間... $browser->waitForLink('Create', 1); ##### 等待頁面跳轉 在使用類似 `$browser->assertPathIs('/home')` 路徑確認時,如果 window.location.pathname 被異步更新,確認就會失敗。可以使用 waitForLocation() 等待頁面跳轉到指定路徑 ```php $browser->waitForLocation('/secret'); ``` 還可以等待被命名的路由跳轉: ```php $browser->waitForRoute($routeName, $parameters); ``` ##### 等待頁面重新加載 如果要在頁面重新加載後確認,可以使用 waitForReload() : ```php $browser->click('.some-action') ->waitForReload() ->assertSee('something'); ``` ##### 等待 JavaScript 表達式 有時會希望暫停執行測試,直到指定的 JavaScript 表達式執行結果為 true。可以使用 waitUntil() 輕易地達成此目的。傳送一個表達式給此方法,不需要包含 return 關鍵字或者結束分號 ```php // 等待表達式為 true 最多 5 秒時間... $browser->waitUntil('App.data.servers.length > 0'); // 等待表達式為 true 最多 1 秒時間... $browser->waitUntil('App.data.servers.length > 0', 1); ``` ##### 等待 Vue 表達式 下述方法可用於一直等待,直到一個指定的 Vue 組件屬性有特定的值: // 一直等待,直到組件屬性有特定的值 $browser->waitUntilVue('user.name', 'goblin', '@user'); // 一直等待,直到組件不包含給定的值 $browser->waitUntilVueIsNot('user.name', null, '@user'); ##### 等待回呼函數 Dusk 中的許多 「wait」 方法都依賴於底層方法 waitUsing()。你可以直接用這個方法去等待一個回呼函數返回 true。waitUsing() 方法接收一個最大的等待秒數,Closure 執行的內部時間,Closure,以及一個可選的失敗訊息 ```php $browser->waitUsing(10, 1, function () use ($something) { return $something->isReady(); }, "Something wasn't ready in time."); ``` #### 滾動到視圖中 有時您可能無法點擊某個元素,因為該元素在瀏覽器的可見區域之外。 scrollIntoView() 可以將元素滾動到瀏覽器可視窗口內: ```php $browser->scrollIntoView('.selector') ->click('.selector'); ``` 滾動到視圖中 有時您可能無法單擊某個元素,因為該元素在瀏覽器的可見區域之外。 scrollIntoView 方法可以將元素滾動到瀏覽器可視窗口內: $browser->scrollIntoView('selector') ->click('selector'); ##### 創建 Vue 斷言 Dusk 甚至還允許你對 Vue 組件資料的狀態進行確認。假設你的應用有如下的 Vue 組件: ``` // HTML結構... <profile dusk="profile-component"></profile> //組件的定義 Vue.component('profile', { template: '<div>{{ user.name }}</div>', data: function () { return { user: { name: 'Taylor' } }; } }); ``` 你可以像這樣對 Vue 組件狀態進行斷言: ```php //Vue 基礎測試案例 public function testVue() { $this->browse(function (Browser $browser) { $browser->visit('/') ->assertVue('user.name', 'Taylor', '@profile-component'); }); } ``` --- ### 可用的斷言 Dusk 提供了各種你可以對應使用的確認方法。所有可用的確認列表如下: #### assertTitle() 確認頁面的標題(title)為指定值 ```php $browser->assertTitle($title); ``` #### assertTitleContains() 確認頁面標題(title)包含指定值 ```php $browser->assertTitleContains($title); ``` #### assertUrlIs() 確認當前的 URL(不包含 query 字串)滿足指定的字串: ```php $browser->assertUrlIs($url); ``` #### assertSchemeIs() 確認當前的 URL scheme 滿足指定的 scheme: ```php $browser->assertSchemeIs($scheme); ``` #### assertSchemeIsNot() 確認當前的 URL scheme 不滿足指定的 scheme: ```php $browser->assertSchemeIsNot($scheme); ``` #### assertHostIs() 確認當前的 URL host 滿足指定的 host: ```php $browser->assertHostIs($host); ``` #### assertHostIsNot() 確認當前的 URL host 不滿足指定的 host: ```php $browser->assertHostIsNot($host); ``` #### assertPortIs() 確認當前的 URL 端口滿足指定的端口: ```php $browser->assertPortIs($port); ``` #### assertPortIsNot() 確認當前的 URL 端口不滿足指定的端口: ```php $browser->assertPortIsNot($port); ``` #### assertPathBeginsWith() 確認當前的 URL 以指定的字串開頭: ```php $browser->assertPathBeginsWith('/home'); ``` #### assertPathIs() 確認當前的路徑滿足指定的路徑: ```php $browser->assertPathIs('/home'); ``` #### assertPathIsNot() 確認當前的路徑不滿足指定的路徑: ```php $browser->assertPathIsNot('/home'); ``` #### assertRouteIs() 確認指定的 URL 不滿足指定的命名路由的 URL: ```php $browser->assertRouteIs($name, $parameters); ``` #### assertQueryStringHas() 確認指定的 query 參數存在: ```php $browser->assertQueryStringHas($name); ``` 確認指定的 query 參數存在,並且是指定的值: ```php $browser->assertQueryStringHas($name, $value); ``` #### assertQueryStringMissing() 確認指定的 query 參數不存在: ```php $browser->assertQueryStringMissing($name); ``` #### assertFragmentIs() 確認當前的 fragment 滿足指定的 fragment: ```php $browser->assertFragmentIs('anchor'); ``` #### assertFragmentBeginsWith() 確認當前的 fragment 以指定的 fragment 開頭: ```php $browser->assertFragmentBeginsWith('anchor'); ``` #### assertFragmentIsNot() 確認當前的 fragment 不滿足指定的 fragment: ```php $browser->assertFragmentIsNot('anchor'); ``` #### assertHasCookie() 確認指定的 cookie 存在: ```php $browser->assertHasCookie($name); ``` #### assertHasPlainCookie() 確認存在指定的未加密 cookie: ```php $browser->assertHasPlainCookie($name); ``` #### assertCookieMissing() 確認指定的 cookie 不存在: ```php $browser->assertCookieMissing($name); ``` #### assertPlainCookieMissing() 確認指定的未加密 cookie不存在: ```php $browser->assertPlainCookieMissing($name); ``` #### assertCookieValue() 確認加密的 cookie 具有指定值: ```php $browser->assertCookieValue($name, $value); ``` #### assertPlainCookieValue() 確認未加密的 cookie 具有指定值: ```php $browser->assertPlainCookieValue($name, $value); ``` #### assertSee() 確認在頁面中有指定的文本: ```php $browser->assertSee($text); ``` #### assertDontSee() 確認在頁面中沒有指定的文字: ```php $browser->assertDontSee($text); ``` #### assertSeeIn() 確認在選擇器中有指定的文字: ```php $browser->assertSeeIn($selector, $text); ``` #### assertDontSeeIn() 確認指定的字串在選擇器中不存在: ```php $browser->assertDontSeeIn($selector, $text); ``` #### assertSeeAnythingIn() 確認在選擇器中存在任何字串: ```php $browser->assertSeeAnythingIn($selector); ``` #### assertSeeNothingIn() 確認在選擇器中不存在任何字串: ```php $browser->assertSeeNothingIn($selector); ``` #### assertScript() 確認指定的 JavaScript 表達式的值為指定的值: ```php $browser->assertScript('window.isLoaded') ->assertScript('document.readyState', 'complete'); ``` #### assertSourceHas() 確認在頁面中存在給定的源代碼: ```php $browser->assertSourceHas($code); ``` #### assertSourceMissing() 確認頁面中沒有給定的源代碼: ```php $browser->assertSourceMissing($code); ``` #### assertSeeLink() 確認在頁面中存在指定的超連結: ```php $browser->assertSeeLink($linkText); ``` #### assertDontSeeLink() 確認頁面中沒有指定的超連結: ```php $browser->assertDontSeeLink($linkText); ``` #### assertInputValue() 確認輸入框(input)有指定的值: ```php $browser->assertInputValue($field, $value); ``` #### assertInputValueIsNot() 確認輸入框沒有指定的值: ```php $browser->assertInputValueIsNot($field, $value); ``` #### assertChecked() 確認複選框(checkbox)有被選中: ```php $browser->assertChecked($field); ``` #### assertNotChecked() 確認複選框(checkbox)沒有被選中: ```php $browser->assertNotChecked($field); ``` #### assertRadioSelected() 確認單選框(radio)被選中: ```php $browser->assertRadioSelected($field, $value); ``` #### assertRadioNotSelected() 確認單選框(radio)沒有被選中: ```php $browser->assertRadioNotSelected($field, $value); ``` #### assertSelected() 確認下拉框有指定的值: ```php $browser->assertSelected($field, $value); ``` #### assertNotSelected() 確認下拉框沒有指定的值: ```php $browser->assertNotSelected($field, $value); ``` #### assertSelectHasOptions() 確認指定的陣列值是可選項: ```php $browser->assertSelectHasOptions($field, $values); ``` #### assertSelectMissingOptions() 確認給定的陣列值是不可選的: ```php $browser->assertSelectMissingOptions($field, $values); ``` #### assertSelectHasOption() 確認指定的值在指定的輸入項是可供選擇的: ```php $browser->assertSelectHasOption($field, $value); ``` #### assertSelectMissingOption() 確認指定的值在指定的輸入項是無法選擇的: ```php $browser->assertSelectMissingOption($field, $value); ``` #### assertValue() 確認選擇器範圍內的元素存在指定的值: ```php $browser->assertValue($selector, $value); ``` #### assertAttribute() 確認與指定選擇器對應的元素在提供的屬性中具有指定的值: ```php $browser->assertAttribute($selector, $attribute, $value); ``` #### assertAriaAttribute() 確認與指定選擇器對應的元素在指定的 aria 屬性中具有指定的值: ```php $browser->assertAriaAttribute($selector, $attribute, $value); ``` 例如,指定標記 <button aria-label="Add"> </button>,你可以像這樣聲明 aria-label 屬性: ```php $browser->assertAriaAttribute('button', 'label', 'Add') ``` #### assertDataAttribute() 確認與指定選擇器對應的元素在提供的 data 屬性中具有指定的值: ```php $browser->assertDataAttribute($selector, $attribute, $value); ``` 例如,指定標記 <tr id="row-1" data-content="attendees"></tr>, 您可以像這樣聲明 data-label 屬性: ```php $browser->assertDataAttribute('#row-1', 'content', 'attendees') ``` #### assertVisible() 確認選擇器範圍內的元素為可見: ```php $browser->assertVisible($selector); ``` #### assertPresent() 確認選擇器範圍內的元素是存在的: ```php $browser->assertPresent($selector); ``` #### assertNotPresent() 確認選擇器範圍內的元素在源代碼是不存在的: ```php $browser->assertNotPresent($selector); ``` #### assertMissing() 確認選擇器範圍內的元素不存在: ```php $browser->assertMissing($selector); ``` #### assertDialogOpened() 確認含有指定訊息的 JavaScript 對話框已經打開: ```php $browser->assertDialogOpened($message); ``` #### assertEnabled() 確認指定的欄位是啟用的: ```php $browser->assertEnabled($field); ``` #### assertDisabled() 確認指定的欄位是停用的: ```php $browser->assertDisabled($field); ``` #### assertButtonEnabled() 確認指定的按鈕是啟用的: ```php $browser->assertButtonEnabled($button); ``` #### assertButtonDisabled() 確認指定的按鈕是關閉的: ```php $browser->assertButtonDisabled($button); ``` #### assertFocused() 確認焦點在於指定的欄位: ```php $browser->assertFocused($field); ``` #### assertNotFocused() 確認焦點不在指定的欄位: ```php $browser->assertNotFocused($field); ``` #### assertAuthenticated() 確認用戶已經授權,即已經登入 ```php $browser->assertAuthenticated(); ``` #### assertGuest() 確認用戶未授權,即尚未登入 ```php $browser->assertGuest(); ``` #### assertAuthenticatedAs() 確認該用戶已通過身份驗證為指定用戶: ```php $browser->assertAuthenticatedAs($user); ``` #### assertVue() 確認 Vue 組件資料的屬性對應指定的值。想像一下,你的應用包含以下 Vue 組件: ``` // HTML結構... <profile dusk="profile-component"></profile> // 定義組件... Vue.component('profile', { template: '<div>{{ user.name }}</div>', data: function () { return { user: { name: 'Taylor' } }; } }); ``` 你能夠確認像這樣去確認 Vue 組件的狀態 ```php //基本Vue測試 public function testVue() { $this->browse(function (Browser $browser) { $browser->visit('/') ->assertVue('user.name', 'Taylor', '@profile-component'); }); } ``` #### assertVueIsNot() 確認 Vue 組件資料的屬性不對應指定的值: ```php $browser->assertVueIsNot($property, $value, $componentSelector = null); ``` #### assertVueContains() 確認 Vue 組件資料的屬性是一個陣列,並且該陣列包含指定的值: ```php $browser->assertVueContains($property, $value, $componentSelector = null); ``` #### assertVueDoesNotContain() 確認 Vue 組件資料的屬性是一個陣列,並且該陣列不包含指定的值: ```php $browser->assertVueDoesNotContain($property, $value, $componentSelector = null); ``` ### 頁面 有時候,需要測試一系列複雜的動作,這會使得測試程式碼難以閱讀和理解。通過頁面(Dusk Page)可以定義出語義化的動作,然後在指定頁面中可以使用單個方法。頁面還可以定義應用或單個頁面通用選擇器的快捷方式 #### 生成頁面 dusk:page 這個 Artisan 命令可以生成頁面物件,所有的頁面物件都位於 tests/Browser/Pages 資料夾內: `php artisan dusk:page Login` #### 配置頁面 頁面預設擁有 3 個方法: url(), assert() 和 elements()。 在這裡我們先詳述 url() 和 assert() , elements() 將會在之後詳加說明 ##### url() url() 應該返回表示頁面 URL 的路徑。 Dusk 將會在瀏覽器中使用這個 URL 來導航到具體頁面: ```php //取得頁面網址 public function url() { return '/login'; } ``` ##### assert() assert() 可以作出任何確認來驗證瀏覽器是否在指定頁面上。這個方法並不是必須的,你可以根據你自己的需求來做出這些確認。這些確認會在你導航到這個頁面的時候自動執行: ```php //確認瀏覽器當前處於指定頁面 public function assert(Browser $browser) { $browser->assertPathIs($this->url()); } ``` #### 導航至頁面 一旦頁面配置好之後,你可以使用 visit() 導航至頁面: ```php use Tests\Browser\Pages\Login; $browser->visit(new Login); ``` 有時您可能已經在指定的頁面上,需要將頁面的選擇器和方法「加載」到當前的測試上下文中。 這在通過按鈕轉址到指定頁面而沒有明確導航到該頁面時很常見。 在這種情況下,您可以使用 on() 去加載頁面: ```php use Tests\Browser\Pages\CreatePlaylist; $browser->visit('/dashboard') ->clickLink('Create Playlist') ->on(new CreatePlaylist) ->assertSee('@create'); ``` #### 選擇器簡寫 elements() 允許你為頁面中的任何 CSS 選擇器定義簡單易記的簡寫。例如,讓我們為應用登入頁中的 email 輸入框定義一個快速且容易記憶的簡寫。例如,讓我們來為應用的登入頁去定義 "email" 輸入項的簡寫: ```php //獲取頁面的元素簡寫 public function elements() { return [ '@email' => 'input[name=email]', ]; } ``` 現在你可以用這個簡寫來代替之前在頁面中使用的完整 CSS 選擇器: ```php $browser->type('@email', 'taylor@laravel.com'); ``` #### 全局的選擇器簡寫 安裝 Dusk 之後,Page 父類別存放在你的 tests/Browser/Pages 資料夾。該類別中包含一個 siteElements() ,這個方法可以用來定義全局的選擇器簡寫,這樣在你應用中每個頁面都可以使用這些全局選擇器簡寫了: //獲取站點全域的選擇器簡寫 public static function siteElements() { return [ '@element' => '#selector', ]; } #### 頁面方法 除了頁面中已經定義的預設方法之外,你還可以定義在整個測試過程中會使用到的其他方法。例如,假設我們正在開發一個音樂管理應用,在應用中都可能需要一個公共的方法來創建列表,而不是在每一頁、每一個測試類別中都重寫一遍創建播放列表的邏輯,這時候你可以在你的頁面類別中定義一個 createPlaylist(): ```php namespace Tests\Browser\Pages; use Laravel\Dusk\Browser; class Dashboard extends Page { // Other page methods... /** * 創建一個新的播放列表. * * @param \Laravel\Dusk\Browser $browser * @param string $name * @return void */ public function createPlaylist(Browser $browser, $name) { $browser->type('name', $name) ->check('share') ->press('Create Playlist'); } } ``` 一旦方法被定義之後,你可以在任何使用到該頁的測試中使用這個方法了。瀏覽器實例會自動作為第一參數傳遞到該頁面方法: ```php use Tests\Browser\Pages\Dashboard; $browser->visit(new Dashboard) ->createPlaylist('My Playlist') ->assertSee('My Playlist'); ``` #### 組件 組件類似於 Dusk 的 「頁面物件」,不過它更多的是貫穿整個應用中頻繁重用的 UI 和功能片段,比如說導航Bar或訊息通知彈窗。因此,組件並不會綁定於某個明確的 URL ##### 生成組件 為了生成一個組件,使用 Artisan 命令 dusk:component 即可生成組件。新生成的組件位於 test/Browser/Components 目錄中: `php artisan dusk:component DatePicker` 如上所示,這是生成一個 「日期選擇器」(date picker) 組件的範例,這個組件可能會貫穿使用在你應用的許多頁面中。在整個測試套件的大量測試頁面中,手動編寫日期選擇的瀏覽器自動化邏輯會非常麻煩。 更方便的替代辦法是,定義一個表示日期選擇器的 Dusk 組件,然後把自動化邏輯封裝在該組件內: namespace Tests\Browser\Components; use Laravel\Dusk\Browser; use Laravel\Dusk\Component as BaseComponent; class DatePicker extends BaseComponent { /** * 獲取組件的根選擇器 * * @return string */ public function selector() { return '.date-picker'; } /** * 確認瀏覽器頁面包含該組件 * * @param Browser $browser * @return void */ public function assert(Browser $browser) { $browser->assertVisible($this->selector()); } /** * 讀取組件的元素捷徑方式 * * @return array */ public function elements() { return [ '@date-field' => 'input.datepicker-input', '@year-list' => 'div > div.datepicker-years', '@month-list' => 'div > div.datepicker-months', '@day-list' => 'div > div.datepicker-days', ]; } /** * 選擇指定日期 * * @param \Laravel\Dusk\Browser $browser * @param int $year * @param int $month * @param int $day * @return void */ public function selectDate(Browser $browser, $year, $month, $day) { $browser->click('@date-field') ->within('@year-list', function ($browser) use ($year) { $browser->click($year); }) ->within('@month-list', function ($browser) use ($month) { $browser->click($month); }) ->within('@day-list', function ($browser) use ($day) { $browser->click($day); }); } } #### 使用組件 組件定義一旦完成,在任何測試頁面的日期選擇器中選定一個日期就很輕鬆了。並且如果需要修改選定日期的邏輯,僅修改該組件即可: ```php //tests\Browser\ExampleTest.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. * * @return void */ public function testBasicExample() { $this->browse(function (Browser $browser) { $browser->visit('/') ->within(new DatePicker, function ($browser) { $browser->selectDate(2019, 1, 30); }) ->assertSee('January'); }); } } ``` #### 持續集成 大部分 Dusk 持續集成設定會預期你的 Laravel 應用使用內建的 PHP 開發伺服器,port 號為8000。因此,在添加持續集成配置文件之前,請確保你的 .env.testing 文件中 APP_URL 配置項的值是 http://127.0.0.1:8000 #### Heroku CI 如果要在 Heroku CI 執行 Dusk 測試,加入以下 Google Chrome buildpack 和腳本到你的 Heroku app.json 檔案: ```json { "environments": { "test": { "buildpacks": [ { "url": "heroku/php" }, { "url": "https://github.com/heroku/heroku-buildpack-google-chrome" } ], "scripts": { "test-setup": "cp .env.testing .env", "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk" } } } } ``` #### Travis CI 如果要在 Travis CI 執行 Dusk 測試,使用以下 .travis.yml 配置檔。因為 Travis CI 不是一個視覺化環境,我們將需要做一些額外的步驟來啟動 Chrome 瀏覽器。除此之外,我們將使用 `php artisan serve` 來啟動 PHP 的內建 Web Server: ``` language: php php: - 7.3 addons: chrome: stable install: - cp .env.testing .env - travis_retry composer install --no-interaction --prefer-dist --no-suggest - php artisan key:generate - 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 If you are using Github Actions to run your Dusk tests, you may use the following configuration file as a starting point. Like TravisCI, we will use the php artisan serve command to launch PHP's built-in web server: name: CI on: [push] jobs: dusk-php: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - 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 --no-suggest --prefer-dist --optimize-autoloader - name: Generate Application Key run: php artisan key:generate - name: Upgrade Chrome Driver run: php artisan dusk:chrome-driver `/opt/google/chrome/chrome --version | cut -d " " -f3 | cut -d "." -f1` - name: Start Chrome Driver run: ./vendor/laravel/dusk/bin/chromedriver-linux & - name: Run Laravel Server run: php artisan serve --no-reload & - name: Run Dusk Tests env: APP_URL: "http://127.0.0.1:8000" run: php artisan dusk - name: Upload Screenshots if: failure() uses: actions/upload-artifact@v2 with: name: screenshots path: tests/Browser/screenshots - name: Upload Console Logs if: failure() uses: actions/upload-artifact@v2 with: name: console path: tests/Browser/console ``` ### 錯誤排除 #### 連線失敗 出現 Failed to connect to localhost port 9515: Connection refused 這樣的訊息錯誤 ##### 可能解決方法: 在 Mac 環境內,Dusk 要求 ChromeDriver 的二進位文件 (binaries) 是可執行的。如果在 Dusk 運行時遇到問題,可以使用以下命令來調整其權限,以確保二進位文件 (binaries) 是可執行的 `chmod -R 0755 vendor/laravel/dusk/bin` #### Chrome Driver 版本錯誤 出現 Facebook\WebDriver\Exception\SessionNotCreatedException: session not created: This version of ChromeDriver only supports Chrome version xx 這樣的錯誤訊息 ##### 可能解決方案: 代表目前Chrome版本,Dusk Driver不支持。可到[這裡](http://chromedriver.chromium.org/downloads)下載當前 Chrome對應的版本,可以到 "關於Google Chrome" 來查詢當前 Chrome 瀏覽器的版本

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully