この記事のゴール
- Laravel Dusk を使ったブラウザテストの仕方が理解できる。
前提条件
- Laravel Dusk の実行環境構築が完了している。
- ブラウザテスト実行対象のWEBアプリ構築が完了している。
まだの場合は、次の記事を参考にしてください。
ここでテストする対象のWEBアプリは、Vue.jsのテュートリアルで作成する
todo を拡張して、Breezeインストール後のDashboard内に実装したものです。
Breezeの代わりにJetstreamでも問題ありません。
テスト対象WEBアプリの機能仕様概要
基本的なCRUD操作を実装しています。追加機能だけをざっくり書きます。
1.新規追加フォーム表示機能:「新規追加」リンク押下で入力フォームがそう入される。
→「Cancel」ボタン押下でフォームは非表示になり「新規追加」リンクが表示される。
2.DB連携:
・TODOデータはDBに保存
・項目毎の編集確定時にDB更新
※Vue.jsテュートリアルのTODOはメモリ上にデータ保持しているだけです。
3.TODO削除直前の確認ダイアログ表示
・「いいえ」を選択した場合はTODOを削除しない。
・「はい」を選択した場合は該当TODOを削除する。
テストコード保存先
Dusk用のブラウザテストコードの保存先は
[プロジェクトトップ]/tests/Browser/
のディレクトリ内です。
![](https://macocci7.net/blog/wp-content/uploads/2023/12/dusk_directory.png)
テストコード例
ざっくりなテストしかしていません。
コードの右側にコメントを付けたので、何をしているのかは大体わかると思います。
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use App\Models\User;
use App\Models\Todo;
class TodoTest extends DuskTestCase
{
private $user;
/**
* 各メソッド実行前の処理
* 参照: https://docs.phpunit.de/en/10.0/fixtures.html#
*/
protected function setUp(): void
{
parent::setUp();
// テスト用ダミーユーザーを1件だけ作成
$this->user = User::factory()->create();
}
/**
* TODO新規追加のテスト
*/
public function test_can_add_todo(): void
{
// 全レコード削除
Todo::truncate();
// 事前データを10件作成
$count = 10;
$todos = Todo::factory()->count($count)->state(['user_id' => $this->user->id])->create();
// ブラウザ操作
$this->browse(function (Browser $browser) {
// スクリーンショット画像ファイル名のベース
$sc_basename = 'test_can_see_todo_';
$browser->loginAs($this->user) // setUp()で作成したユーザーでログイン
->visitRoute('todo.index') // TODOページへアクセス
->screenshot($sc_basename . '000') // スクリーンショット保存
->assertSee(config('site.title')) // サイトタイトル確認
->assertSee(config('site.copyright')) // コピーライト確認
->waitFor('#todolist',10) // 10秒間のレンダリング待ち(待ちすぎ?)
->screenshot($sc_basename . '001')
->click('.addText') // 「新規追加」リンクをクリック
->screenshot($sc_basename . '002')
->type('.newTodoTitle','newTodoTitle') // 件名入力
->type('.newTodoDescription','newTodoDescription') // 内容入力
->screenshot($sc_basename . '003')
->press('.addTodoSubmit') // 追加ボタン押下
->pause(500) // 500ミリ秒(0.5秒)処理待ち
->with('#todolist', function (Browser $table) { // スコープ絞り込み
$table->assertSee('newTodoTitle') // 新規件名の表示確認
->assertSee('newTodoDescription'); // 新規内容の表示確認
})
->screenshot($sc_basename . '004')
;
});
}
public function test_can_set_done():void
{
Todo::truncate();
$count = 10;
$todos = Todo::factory()->count($count)->state(['user_id' => $this->user->id, 'done' => 0])->create();
$this->browse(function (Browser $browser) {
$sc_basename = 'test_can_set_done_';
$browser->loginAs($this->user)
->visitRoute('todo.index')
->pause(500)
->screenshot($sc_basename . '001')
->check('.check-done:first-child') // 最初のチェックボックスをチェック
->check('.check-done:last-child') // 最後のチェックボックスをチェック
->screenshot($sc_basename . '002');
$browser->loginAs($this->user)
->visitRoute('todo.index')
->pause(500)
->screenshot($sc_basename . '003')
->assertChecked('.check-done:first-child') // チェックボックスのチェック確認
->assertChecked('.check-done:last-child');
});
}
public function test_can_edit_todo():void
{
Todo::truncate();
$count = 10;
$todos = Todo::factory()->count($count)->state(['user_id' => $this->user->id, 'done' => 0])->create();
$this->browse(function (Browser $browser) {
$sc_basename = 'test_can_edit_todo_';
$browser->loginAs($this->user)
->visitRoute('todo.index')
->pause(500)
->screenshot($sc_basename . '001')
->doubleClick('.todo-title-text:first-child') // 最初のTODO件名をダブルクリック
->screenshot($sc_basename . '002')
//->type('todoTitle', 'duskhogehoge') // This doesn't work.
//->type('.todo-input-title','duskhogehoge') // This doesn't work.
//->value('.todo-input-title',"duskhogehoge") // This doesn't work.
->value('.todo-input-title','') // 件名入力欄のvalue属性値を''にする
->keys('.todo-input-title','duskhogehoge','{ENTER}') // 値を入力して[ENTER]押下
->pause(1000) // 1000ミリ秒(1秒)のDB更新待ち
->screenshot($sc_basename . '003');
$browser->loginAs($this->user)
->visitRoute('todo.index')
->pause(500)
->screenshot($sc_basename . '004')
->assertSeeIn('.todo-title-text:first-child','duskhogehoge')
->doubleClick('.todo-description-text:first-child')
->screenshot($sc_basename . '005')
->clear('.todo-textarea-description') // テキストエリア内消去
->type('.todo-textarea-description','duskduskhogehoge')
->screenshot($sc_basename . '006')
->press('.cancel-edit-todo-description')
->screenshot($sc_basename . '007')
->assertDontSeeIn('.todo-description-text:first-child','duskduskhogehoge') // 表示されてない確認
->doubleClick('.todo-description-text:first-child')
->clear('.todo-textarea-description')
->type('.todo-textarea-description','duskduskhogehoge')
->press('.save-todo-description')
->pause(1000)
->screenshot($sc_basename . '008');
$browser->loginAs($this->user)
->visitRoute('todo.index')
->pause(500)
->screenshot($sc_basename . '009')
->assertSeeIn('.todo-description-text:first-child','duskduskhogehoge');
});
}
public function test_can_delete_todo():void
{
Todo::truncate();
$count = 10;
$todos = Todo::factory()->count($count)->state(['user_id' => $this->user->id, 'done' => 0])->create();
$this->browse(function (Browser $browser) {
$sc_basename = 'test_can_delete_todo_';
$browser->loginAs($this->user)
->visitRoute('todo.index')
->screenshot($sc_basename . '001')
->doubleClick('.todo-title-text')
->value('.todo-input-title','')
->keys('.todo-input-title','duskhogehoge','{ENTER}')
->pause(500)
->screenshot($sc_basename . '002')
->press('.remove-todo:first-child')
->waitForDialog(3) // ダイアログの表示待ち3秒
->assertDialogOpened('「duskhogehoge」の削除を実行しますか?') // ダイアログ内容確認
//->screenshot($sc_basename . '003') // ここではExceptionスローされる
->dismissDialog() // ダイアログで「いいえ」を選択
->assertSeeIn('.todo-title-text:first-child','duskhogehoge')
->screenshot($sc_basename . '003')
->press('.remove-todo:first-child')
->waitForDialog(3)
->acceptDialog() // ダイアログで「はい」を選択
->pause(1000)
->screenshot($sc_basename . '004')
->assertDontSeeIn('.todo-title-text:first-child','duskhogehoge');
});
}
}
テスト実行
プロジェクトトップディレクトリ内で、ターミナルのコマンドプロンプト上で、
次のコマンドを実行します。
./vendor/bin/sail dusk
![](https://macocci7.net/blog/wp-content/uploads/2023/12/dusk_run.png)
筆者の実行環境が貧弱なので1分掛かってしまいましたが、
無事オールグリーンです。
スクリーンショット
保存先は次のディレクトリ内です。
[プロジェクトトップ]/tests/Browser/screenshots/
![](https://macocci7.net/blog/wp-content/uploads/2023/12/dusk_screenshots.png)
ここに保存される画像は、テスト実行毎に一度全削除されます。
スクリーンショットの例をいくつか見てみましょう。
▼TODO一覧表示
![](https://macocci7.net/blog/wp-content/uploads/2023/12/test_can_see_todo_001-1024x511.png)
画面が幅広ですね。
縦方向のスクロールバーがついているので、
ページ全体のスクリーンショットではないことがわかります。
ページをスクロールさせるメソッドもあるので、
画面最下部に特定の要素を配置して、
scrollIntoView() メソッドなどを使うと良いかもしれません。
▼新規追加入力フォーム
![](https://macocci7.net/blog/wp-content/uploads/2023/12/test_can_see_todo_003-1024x511.png)
▼内容テキストダブルクリック後の編集フォーム
![](https://macocci7.net/blog/wp-content/uploads/2023/12/test_can_edit_todo_005-1024x511.png)
▼全件削除後の表示
![](https://macocci7.net/blog/wp-content/uploads/2023/12/test_can_delete_todo_001-1024x511.png)
このような感じで、スクリーンショットが保存されます。
コメント