前回の続きで、今回はLaravel Duskを使用して、非同期なテストを実装したいと思います。以前調査した際に、重い処理がある場合には、待機する必要があることが分かったので、Laravel Duskで非同期で重い処理を待機する方法を紹介します。
Ajax通信を行った後の画面を検証したり、操作したりしたい場合は、JavaScriptの式を実行し、結果がtrueになるまで待機することができます。
index.blade.php
<div id="result"></div> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script> var books = []; $.ajax({ url: 'http://localhost/api/books', success: function(response) { books = response; console.log(books); var bookList = '<table>' + ' <tr>' + ' <th>ID</th>' + ' <th>タイトル</th>' + ' <th>著者</th>' + ' <th colspan="2"></th>' + ' </tr>'; books.forEach(book => { bookList += '<tr>' + '<td>' + book.id + '</td>' + '<td>' + book.title + '</td>' + '<td>' + book.author + '</td>' + '<td><a href="/books/' + book.id + '">詳細</a></td>' + '</tr>'; }); bookList += '</table>'; $('#result').html(bookList); } }); </script>
Ajax通信では次のJSONがレスポンスされます。
[ { "id": 3, "title": "タイトルテスト", "author": "著者テスト", "is_check_out": false, "created_at": "2020-10-23T22:50:47.000000Z", "updated_at": "2020-10-24T01:26:17.000000Z" } ]
今回は、ページが読み込まれて、書籍の一覧表示をテストします。
class BookTest extends DuskTestCase { public function testIndex() { $this->browse(function (Browser $browser) { $browser->visit('/books') ->waitUntil('books.length > 0') ->assertSee('タイトルテスト'); }); } }
waitUntil()
メソッドで、JavaScriptの式を渡し、その結果がtrueになると、後続が実行されます。waitUntil('books.length > 0', 10)
というように、第二引数を設定すると、10秒でタイムアウトするようになります。
では、この式がどのような仕組みで評価されているか理解するために、 waitUntil()
メソッドのコードを読んでいきたいと思います。
/** * Wait until the given script returns true. * * @param string $script * @param int $seconds * @param string $message * @return $this * * @throws \Facebook\WebDriver\Exception\TimeOutException */ public function waitUntil($script, $seconds = null, $message = null) { if (! Str::startsWith($script, 'return ')) { $script = 'return '.$script; } if (! Str::endsWith($script, ';')) { $script = $script.';'; } return $this->waitUsing($seconds, 100, function () use ($script) { return $this->driver->executeScript($script); }, $message); }
まず、コードの整形が行われ、式に return
がない場合は、先頭に着けます。セミコロンも同様に付けます。
その後、 waitUsing()
メソッドを使って、100msごとに、WebDriverにJavaScriptを実行させます。waitUsing()
メソッドは、クロージャの中で true
が返却されるか、タイムアウトすると終了します。
ちなみに、タイムアウトまでの秒数が指定されていない場合は、 waitUsing()
メソッドのデフォルト値である5秒でタイムアウトします。
DOMの表示を待ち、その結果を検証することもできます。
先ほどのbladeファイルを変更します。
<div id="result"></div> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script> var books = []; function index() { $.ajax({ url: '/api/books', success: function(response) { books = response; console.log(books); var bookList = '<table>' + ' <tr>' + ' <th>ID</th>' + ' <th>タイトル</th>' + ' <th>著者</th>' + ' <th colspan="2"></th>' + ' </tr>'; books.forEach(book => { bookList += '<tr>' + '<td>' + book.id + '</td>' + '<td>' + book.title + '</td>' + '<td>' + book.author + '</td>' + '<td><a href="/books/' + book.id + '">詳細</a></td>'; if (book.is_check_out) { bookList += '<td><button type="button" class="return" data-id="'+book.id+'">返却する</button></td>'; } else { bookList += '<td><button type="button" class="checkout" data-id="'+book.id+'">借りる</button></td>' } bookList += '</td>'; }); bookList += '</table>'; $('#result').html(bookList); } }); } // 借りるボタンをクリック $(document).on('click', '.checkout', function() { const id = $(this).data('id'); $.ajax({ url: '/api/books/' + id + '/check_out', type: 'POST', success: function(response) { index(); } }); }); // 返却ボタンをクリック $(document).on('click', '.return', function() { const id = $(this).data('id'); $.ajax({ url: '/api/books/' + id + '/return', type: 'POST', success: function(response) { index(); } }); }); index(); </script>
本を借りたり、返却したりすることができるようになりました。この借りる・返却するボタンのテストを実装したいと思います。
class BookTest extends DuskTestCase { public function testCheckOutBook() { $this->browse(function (Browser $browser) { $browser->visit('/books') ->waitUntil('books.length > 0') ->click('.checkout') ->waitForText('返却する') ->assertSee('返却する'); }); } public function testReturnBook() { $this->browse(function (Browser $browser) { $browser->visit('/books') ->waitUntil('books.length > 0') ->click('.return') ->waitForText('借りる') ->assertSee('借りる'); }); }
まず、借りる/返却するボタンが表示されるのを待つため、一覧表示のテストと同じように待機します。
ボタン動作の検証ですが、今回は、ボタンの文言が借りた後は「返却する」、返却した後は「借りる」に変わっていることを検証するため、 waitForText
を使用して待機させています。waitForText('返却する', 10)
とすると、10秒でタイムアウトになります。
この他にも、 waitFor('.return')
のように、CSSセレクタを使用して待機させることも可能です。
waitForTextのコードも見てみましょう。
/** * Wait for the given text to be visible. * * @param array|string $text * @param int $seconds * @return $this * * @throws \Facebook\WebDriver\Exception\TimeOutException */ public function waitForText($text, $seconds = null) { $text = Arr::wrap($text); $message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text)); return $this->waitUsing($seconds, 100, function () use ($text) { return Str::contains($this->resolver->findOrFail('')->getText(), $text); }, $message); }
先ほどと同じように waitUsing()
メソッドを使って、100msおきに Str::conatins()
関数により評価され、テキストが存在してtrueが返却されるか、タイムアウトでfalseが返却されます。
いかがでしたか。Laravel Duskで、シンプルな非同期処理の待機を実装することができます。ぜひ試してみてください。