今回は、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