PuPHPeteerでE2Eテスト(7) 複数タブとヘッドレス

今回は、3つのタブを開き、それぞれのタブでGoogle検索、Jpostalで住所入力、Amazon検索をするテストプログラムを作ります。

PuPHPeteerとは

WebアプリケーションのE2Eテストフレームワークの一つに、Puppeteer(パペッティア)があります。Puppeteerはブラウザを起動し、ブラウザを操作してE2Eテストをします。

PuPHPeteerは、PHPからPuppeteerを使うためのライブラリです。

前回までのテスト

前回は、シナリオトレイトを使って、amazonで「テスト駆動開発」と「実践テスト駆動開発」の2冊をカートに入れるテストプログラムを作りました。

今回のテスト

タブを3つ表示します。1つめのタブでAmazonカート、2つめのタブでGoogle検索、3つめのタブでJpostal住所入力します。

実際のテストでは、1つめのタブで一般ユーザ向けページ、2つめのタブで管理画面、3つめのタブで外部APIサービスの管理画面というように使います。

1つのタブで、複数サイトをテストすることもできますが、それぞれのサイトごとにタブを用意したほうが、それぞれのサイトの表示確認をしやすく、テスト作成に便利です。

3つのタブを表示する

これまでは、テストごとにブラウザを新しく起動して、タブが1つ表示していました。Puppeteerの初期化をしている箇所は、BasePuppeteerTestCaseのsetUpメソッドで、次のとおりです。

abstract class BasePuppeteerTestCase extends TestCase { ... public function setUp(): void { parent::setUp(); $puppeteer = new Puppeteer(); $this->browser = $puppeteer->launch($this->launchOptions()); $this->page = ($this->browser->pages())[0]; }
Code language: plaintext (plaintext)

このBasePuppeteerTestCase.phpに、selectPageメソッドを追加します。

開いているタブの一覧を取得して、$this->page に操作したいタブを設定します。

abstract class BasePuppeteerTestCase extends TestCase { ... protected function selectPage(int $n): void { $pages = $this->browser->pages(); $this->page = $pages[$n]; }
Code language: plaintext (plaintext)

1つしかタブを開いていないのに、$pages[3]を指定したら、「Undefined offset」エラーで停止するよね

配列の範囲チェックはしないの?

配列の範囲チェックをしても、親切なメッセージを表示するぐらいね。範囲チェックは不要よ。

前回のAmazon2Test.phpをコピペして、新しくGoogleJpostalAmazonTest.phpを作ります。

テストメソッドを「test_3タブ」に編集します。

<?php namespace Ninton\Test; use Ninton\Test\Libs\Amazon\SearchScenario; class GoogleJpostalAmazonTest extends BasePuppeteerTestCase { use SearchScenario; public function test_3タブ(): void { $this->本を検索してカートに入れる('テスト駆動開発'); $this->assertカート追加直後の小計('3080'); $this->本を検索してカートに入れる('実践テスト駆動開発'); $this->assertカート追加直後の小計('7700'); } }
Code language: plaintext (plaintext)

次に、setUpメソッドを作り、2つのタブを新規作成します。すでに1つのタブが表示されているので、合計3つのタブが表示されます。

1つめのタブで、amazonカートのテストをするので、「$this->selectPage(0)」を書きます。

<?php namespace Ninton\Test; use Ninton\Test\Libs\Amazon\SearchScenario; class GoogleJpostalAmazonTest extends BasePuppeteerTestCase { use SearchScenario; public function setUp(): void { parent::setUp(); $this->browser->newPage(); $this->browser->newPage(); } public function test_3タブ(): void { $this->selectPage(0); $this->本を検索してカートに入れる('テスト駆動開発'); $this->assertカート追加直後の小計('3080'); $this->本を検索してカートに入れる('実践テスト駆動開発'); $this->assertカート追加直後の小計('7700'); } }
Code language: plaintext (plaintext)

テストを実行します。

$ ./phpunit.sh tests/GoogleJpostalAmazonTest.php + ./vendor/bin/phpunit --configuration=phpunit.xml tests/GoogleJpostalAmazonTest.php PHPUnit 8.5.14 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 22.64 seconds, Memory: 6.00 MB OK (1 test, 2 assertions)
Code language: Bash (bash)

ブラウザが起動し、3つのタブが表示されて、1つめのタブでamazonカートのテストが実行されました。

2つめのタブでGoogle検索

2つめのタブに切り替えるために、テストメソッドに「$this->selectPage(1);」を書きます。

GoogleSearchTest.phpから、テストメソッドの内容をコピペします。

public function test_3タブ(): void { $this->selectPage(0); $this->本を検索してカートに入れる('テスト駆動開発'); $this->assertカート追加直後の小計('3080'); $this->本を検索してカートに入れる('実践テスト駆動開発'); $this->assertカート追加直後の小計('7700'); $this->selectPage(1); $this->page->goto('https://www.google.co.jp'); $this->screenShot(); $selector = 'input[name="q"]'; $this->page->type($selector, 'PuPHPeteer'); $selector = 'input[name="btnK"]'; $this->page->click($selector); $this->waitForPageLoad(); $this->screenShot(); $selector = 'div.tF2Cxc:nth-child(2) > div:nth-child(1) > a:nth-child(1) > h3:nth-child(2)'; $el = $this->page->querySelector($selector); $text = $el->getProperty('textContent')->jsonValue(); $this->assertStringContainsString('rialto-php/puphpeteer', $text); }
Code language: plaintext (plaintext)

テストを実行します。

$ ./phpunit.sh tests/GoogleJpostalAmazonTest.php + ./vendor/bin/phpunit --configuration=phpunit.xml tests/GoogleJpostalAmazonTest.php PHPUnit 8.5.14 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 22.64 seconds, Memory: 6.00 MB OK (1 test, 2 assertions)
Code language: Bash (bash)

ブラウザが起動し、3つのタブが表示されて、1つめのタブでamazonカート、2つめのタブでGoogle検索が実行されました。

シナリオトレイトとページオブジェクトを作って、リファクタします。

まず、検索ページのページオブジェクトモデル Libs/Google/IndexPage.phpを作ります。

<?php namespace Ninton\Test\Libs\Google; use Nesk\Puphpeteer\Resources\Page; use Ninton\Test\Libs\BasePage; class IndexPage extends BasePage { public static function goto(Page $page): IndexPage { $page->goto('https://www.google.co.jp'); return new IndexPage($page); } public function type検索ワード(string $text): void { $selector = 'input[name="q"]'; $this->page->type($selector, 'PuPHPeteer'); } public function click検索ボタン(): void { $selector = 'input[name="btnK"]'; $this->page->click($selector); } }
Code language: plaintext (plaintext)

検索結果ページの Libs/Google/ListPage.phpを作ります。

<?php namespace Ninton\Test\Libs\Google; use Ninton\Test\Libs\BasePage; class ListPage extends BasePage { public function get検索結果の1つめ(): string { $selector = 'div.tF2Cxc:nth-child(2) > div:nth-child(1) > a:nth-child(1) > h3:nth-child(2)'; $el = $this->page->querySelector($selector); $text = $el->getProperty('textContent')->jsonValue(); return $text; } }
Code language: plaintext (plaintext)

この2つのページオブジェクトモデルを使って、シナリオトレイト Libs/Google/SearchScenario.phpを作ります。

<?php namespace Ninton\Test\Libs\Google; trait SearchScenario { private function google検索する(string $text): void { $indexPage = IndexPage::goto($this->page); $this->screenShot(); $indexPage->type検索ワード($text); $indexPage->click検索ボタン(); $this->waitForPageLoad(); $this->screenShot(); } private function assertGoogle検索結果にあるはず(string $expectedText): void { $listPage = new ListPage($this->page); $text = $listPage->get検索結果の1つめ(); $this->assertStringContainsString($expectedText, $text); } }
Code language: plaintext (plaintext)

シナリオトレイトを使って、テストメソッドをリファクタします。

<?php namespace Ninton\Test; use Ninton\Test\Libs\Amazon\SearchScenario; use Ninton\Test\Libs\Google\SearchScenario as GoogleSearchScenario; class GoogleJpostalAmazonTest extends BasePuppeteerTestCase { use SearchScenario; use GoogleSearchScenario; public function setUp(): void { parent::setUp(); $this->browser->newPage(); $this->browser->newPage(); } public function test_3タブ(): void { $this->selectPage(0); $this->本を検索してカートに入れる('テスト駆動開発'); $this->assertカート追加直後の小計('3080'); $this->本を検索してカートに入れる('実践テスト駆動開発'); $this->assertカート追加直後の小計('7700'); $this->selectPage(1); $this->google検索する('PuPHPeteer'); $this->assertGoogle検索結果にあるはず('rialto-php/puphpeteer'); } }
Code language: plaintext (plaintext)

テストを実行してみると、失敗しました。

検索結果の1番めに「rialto-php/puphpeteer」が表示されることを想定していましたが、2番目に表示されたからですね。検索結果の順位は変動するので仕方がありません。

検索結果から先頭10個のH3タグを探して、テキスト配列を返すようにします。どれかに「rialto-php/puphpeteer」が含まれていることを検証することにします。

Libs/Google/ListPage.phpに新しいメソッドを追加します。

class ListPage extends BasePage { ... public function get検索結果のh3テキスト配列(int $len = 10): array { $selector = 'div.tF2Cxc h3'; $elArr = $this->page->querySelectorAll($selector); $elArr = array_slice($elArr, 0, $len); $textArr = array_map(function ($el) { $text = $el->getProperty('textContent')->jsonValue(); return $text; }, $elArr); return $textArr; } }
Code language: plaintext (plaintext)

Libs/Google/SearchScenario.phpの「assertGoogle検索結果にあるはず」メソッドを変更します。

trait SearchScenario { ... private function assertGoogle検索結果にあるはず(string $expectedText): void { $listPage = new ListPage($this->page); $textArr = $listPage->get検索結果のh3テキスト配列(); $containsArr = array_filter($textArr, function ($text) use ($expectedText) { return str_contains($text, $expectedText); }); $containsArr = array_values($containsArr); $this->assertTrue(1 <= count($containsArr)); }
Code language: plaintext (plaintext)

テストが通るようになりました。

3つめのタブでJpostal住所入力

Jpostal100v3Testのテストメソッドからシナリオトレイトを作ります。

Libs/Jpostal/Sample1PageScneario.php

<?php namespace Ninton\Test\Libs\Jpostal; trait Sample1PageScneario { private function jpostal_1000001は東京都千代田区千代田(): void { $sample1Page = Sample1Page::goto($this->page); $sample1Page->type郵便番号の上3桁('100'); sleep(2); $this->screenShot(); $address1 = $sample1Page->get都道府県value(); $this->assertEquals('東京都', $address1); $address2 = $sample1Page->get市区町村value(); $this->assertEquals('千代田区', $address2); $sample1Page->type郵便番号の下4桁('0001'); usleep(100000); $this->screenShot(); $address3 = $sample1Page->get町域value(); $this->assertEquals('千代田', $address3); $sample1Page->type郵便番号の下4桁('0002'); usleep(100000); $this->screenShot(); $address3 = $sample1Page->get町域value(); $this->assertEquals('皇居外苑', $address3); } }
Code language: plaintext (plaintext)

このシナリオトレイトを使って、テストメソッドに追加します。

<?php namespace Ninton\Test; use Ninton\Test\Libs\Amazon\SearchScenario; use Ninton\Test\Libs\Google\SearchScenario as GoogleSearchScenario; use Ninton\Test\Libs\Jpostal\Sample1PageScneario; class GoogleJpostalAmazonTest extends BasePuppeteerTestCase { use SearchScenario; use GoogleSearchScenario; use Sample1PageScneario; public function setUp(): void { parent::setUp(); $this->browser->newPage(); $this->browser->newPage(); } public function test_3タブ(): void { $this->selectPage(0); $this->本を検索してカートに入れる('テスト駆動開発'); $this->assertカート追加直後の小計('3080'); $this->本を検索してカートに入れる('実践テスト駆動開発'); $this->assertカート追加直後の小計('7700'); $this->selectPage(1); $this->google検索する('PuPHPeteer'); $this->assertGoogle検索結果にあるはず('rialto-php/puphpeteer'); $this->selectPage(2); $this->jpostal_1000001は東京都千代田区千代田(); } }
Code language: plaintext (plaintext)

ヘッドレスでテストする

tests/BasePuppeteerTestCase.phpのlaunchOptionsメソッドを編集します。

protected function launchOptions(): array { return [ 'defaultViewport' => [ 'width' => 0, 'height' => 0, ], 'headless' => false, 'ignoreHTTPSErrors' => true, 'slowMo' => 50, 'args' => [ '--no-sandbox', '--disable-setuid-sandbox', "--window-size=1280,1024", ], ]; }
Code language: plaintext (plaintext)

'headless' => true に編集します。

protected function launchOptions(): array { return [ 'defaultViewport' => [ 'width' => 0, 'height' => 0, ], 'headless' => true, 'ignoreHTTPSErrors' => true, 'slowMo' => 50, 'args' => [ '--no-sandbox', '--disable-setuid-sandbox', "--window-size=1280,1024", ], ]; }
Code language: plaintext (plaintext)

テストを実行すると、ブラウザは起動しませんが、テストが合格し、スクリーンショットがtmp/に保存されました。

Jpostalのヘッドレスのスクリーンショットは、レイアウトが崩れていました。参考までに表示します。

まとめ

tests/GoogleJpostalAmazonTest.php
https://github.com/ninton/study_puphpeteer/blob/main/tests/GoogleJpostalAmazonTest.php

tests/Libs/Google/SearchScenario.php
https://github.com/ninton/study_puphpeteer/blob/main/tests/Libs/Google/SearchScenario.php

tests/Libs/Google/IndexPage.php
https://github.com/ninton/study_puphpeteer/blob/main/tests/Libs/Google/IndexPage.php

tests/Libs/Google/ListPage.php
https://github.com/ninton/study_puphpeteer/blob/main/tests/Libs/Google/ListPage.php

タイトルとURLをコピーしました