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をコピーしました