PuPHPeteerでE2Eテスト(2) Google検索

Googleで「PuPHPeteer」を検索するテストプログラムを作ります。

PuPHPeteerとは

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

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

準備

npm init

study_puphpeteerディレクトリを作成して、cdします。

$ mkdir study_puphpeteer $ cd study_puphpeteer
Code language: Bash (bash)

npmで @nesk/puphpeteerをインストールします。

$ npm init -y $ npm install @nesk/puphpeteer
Code language: Bash (bash)

composer init

composer initします。各項目に適当なものを入力して、dependenciesは "n"、dev devendenciesも"n" を入力します。

$ composer init ... Define your dependencies. Would you like to define your dependencies (require) interactively [yes]? n Would you like to define your dev dependencies (require-dev) interactively [yes]? n
Code language: Bash (bash)

開発環境のPHPバージョンを調べて、composer.jsonに設定します。

puphpeteerの最新バージョンは、PHP7.3以上が必要です。開発環境のPHPバージョンが古い場合、それにあわせてpuphpeteerの古いバージョンをインストールする必要があります。

config.platform.php で7.2を指定しておくと、puphpeteerのバージョンのなかからPHP7.2に対応しているバージョンを探してインストールしてくれます。

$ php -v PHP 7.2.34-18+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Feb 23 2021 15:08:03) ( NTS ) Copyright (c) 1997-2018 The PHP Group
Code language: Bash (bash)

この場合は、7.2 なので、

$ composer config platform.php 7.2
Code language: Bash (bash)

composer.jsonの config項目に、PHPバージョンが設定されました。

$ cat composer.json { ... "config": { "platform": { "php": "7.2" } } }
Code language: Bash (bash)

composer.jsonをテキストエディターで編集して、testsディレクトリにnamespaceを設定します。

"autoload-dev": { "psr-4": { "Ninton\\Test\\": "tests/" } } }
Code language: plaintext (plaintext)

composerパッケージをインストールします。

$ comspoer require nesk/puphpeteer $ comspoer require phpunit/phpunit
Code language: Bash (bash)

最初のテスト

ここでは、3つのファイルを作成します。必ず合格する単純なテストを書いて、テストが動くことを確認します。

study_puphpeteer ├── composer.json ├── node_modules ├── package.json ├── phpunit.sh ★コレ ├── phpunit.xml ★コレ ├── tests │   └── GoogleSearchTest.php ★コレ └── vendor
Code language: plaintext (plaintext)

tests/GoogleSearchTest.php

tests/GoogleSearchTest.php を新規作成します。必ず合格する単純なテストを書きます。

<?php namespace Ninton\Test; use PHPUnit\Framework\TestCase; class GoogleSearchTest extends TestCase { public function testSmoke(): void { $this->assertTrue(true); } }
Code language: PHP (php)

phpunit.xml

phpunit.xmlを新規作成します。

bootstrap属性にautoload.phpの場所を設定します。

また、テストファイルは、tests/下の xxxTest.phpのファイルであることを設定します。

<?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false"> <testsuites> <testsuite name="study_puphpeteer"> <directory suffix="Test.php">./tests</directory> </testsuite> </testsuites> </phpunit>
Code language: HTML, XML (xml)

phpunit.sh

3つめのファイル、phpunit.shを新規作成します。

$ touch phpunit.sh $ chmod +x phpunit.sh
Code language: Bash (bash)

次の内容に編集して、保存します。

#!/bin/bash -ux ./vendor/bin/phpunit \ --configuration=phpunit.xml \ $1
Code language: Bash (bash)

テスト実行

ターミナルでテスト実行します。最後にグリーンで「OK (1 test, 1 assertion)」と表示されたら、テスト合格です!

$ ./phpunit.sh tests/GoogleSearchTest.php + ./vendor/bin/phpunit --configuration=phpunit.xml tests/GoogleSearchTest.php PHPUnit 8.5.14 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 38 ms, Memory: 4.00 MB OK (1 test, 1 assertion)
Code language: Bash (bash)

やっと準備ができたんだね

Googleを表示して「PuPHPeteer」で検索する

まず、実際に、人の手で操作してみます。

  • ブラウザを起動します。
  • https://www.google.co.jp を表示します。
  • 検索ワード欄に "PuPHPeteer" と入力します。
  • 「Google 検索」ボタンをクリックします。
  • 一覧の先頭に「rialto-php/puphpeteer」が表示されています。この位置に「rialto-php/puphpeteer」が表示されていることを検証します。
  • ブラウザを閉じます。

ブラウザを起動する、ブラウザを閉じる

setUpメソッドを書いて、ブラウザを起動します。

ブラウザが起動した時点で、すでにタブが1つ表示されているので、そのタブを取得します。それが $this->page です。

<?php namespace Ninton\Test; use Nesk\Puphpeteer\Puppeteer; use Nesk\Puphpeteer\Resources\Browser; use Nesk\Puphpeteer\Resources\Page; use PHPUnit\Framework\TestCase; class GoogleSearchTest extends TestCase { /** * @var Browser */ private $browser; /** * @var Page */ private $page; public function setUp(): void { parent::setUp(); $puppeteer = new Puppeteer(); $this->browser = $puppeteer->launch($this->launchOptions()); $this->page = ($this->browser->pages())[0]; }
Code language: PHP (php)

ブラウザの起動オプションです。

'headless' => false にすると、ブラウザを表示します。

private 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: PHP (php)

tearDownメソッドを書いて、ブラウザを終了します。

public function tearDown(): void { $this->browser->close(); parent::tearDown(); }
Code language: PHP (php)

testSmokeメソッドを残したまま、ここでテスト実行してみると、

public function testSmoke(): void { $this->assertTrue(true); } }
Code language: PHP (php)

ブラウザが起動して、すぐに閉じるはずです。

ブラウザを操作する

testSmokeメソッドは削除してください。

その代わりに、次のメソッドを追加します。

public function test_GoogleでPuPHPeteer検索するとrialto_php_puphpeteerがあること(): void { }
Code language: PHP (php)

メソッド名に日本語を使っていいの?

JUnit実践入門」で、テスト用のメソッド名は日本語で書いたほうがいいよ、と説明していて、広く受け入れられているわ。

このメソッドにブラウザ操作を書いていきます。

まず、www.google.co.jp を表示します。

Pageクラスのgotoメソッドを使います。

$this->page->goto('https://www.google.co.jp');
Code language: PHP (php)

検索ワード欄に "PuPHPeteer" と入力します。

テキスト入力は、Pageクラスのtypeメソッドを使います。DOM要素の指定には、CSSセレクターを使います。DOM idがあれば一番確実で、'#search-word' のように指定します。

フォームのinputタグは、nameやvalueを指定することができます。

$selector = 'input[name="q"]'; $this->page->type($selector, 'PuPHPeteer');
Code language: PHP (php)

「Google 検索」ボタンをクリックします。

クリックは、Pageクラスのclickメソッドを使います。

$selector = 'input[name="btnK"]'; $this->page->click($selector);
Code language: PHP (php)

clickメソッドを呼んだ後、検索結果が表示されるまで待たなくてはなりません。「次のページを待つ」のはさまざまな状況があり、Puppeteerを使ったE2Eテストのなかで難しい処理の一つです。いろいろ試した結果、たんに sleep(5); に落ち着くこともあります。

ここでは、onLoadイベント、通信がアイドルになってから500ミリ秒経過、を待つことにします。

$this->page->waitForNavigation("{waitUntil: ['load', 'networkidle2']}");
Code language: PHP (php)

検索結果に「rialto-php/puphpeteer」があるかどうか検証します。

PageクラスのquerySelectorメソッドで、DOM要素を取得します。見つからなければ null が返ってきます。

$el->getProperty('textContent')->jsonValue()で、DOM要素のテキストが返ってきます。

$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: PHP (php)

DOM idがなく、INPUTタグでもない場合、HTMLソースを読んで、CSSセレクターを組み立てるのは困難です。Google Chromeのdev toolで調べます。

Google Chromeのdev toolを表示します。
dev toolのElementsタブでDOM要素を探します。
DOM要素の上で右クリック>Copy>Copy Selector
の手順で、そのDOM要素のCSSセレクターを取得できます。

DOM要素の順番や階層構造が変化したら、CSSセレクターも変わるよね?

そうね

さっきはDOM要素をとれてたのに、デバグ表示をするとDOM要素をとれなくなって、しばらく迷ってしまいことがあるわね

ここでテスト実行してみましょう。ブラウザが起動して、自動で文字をタイプして、クリックして、検索結果が表示されるはずです。

筆者の環境では、約5秒かかりました。

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

自動でブラウザが動いているのを見ると、おー!って感心してしまうよ

スクリーンショット

Puppeteerはスクリーンショットを保存することができます。ヘッドレスで実行したとき、スクリーンショットをとってあると、テスト失敗の原因を調べやすくなります。

study_puphpeteer ├── composer.json ├── node_modules ├── package.json ├── phpunit.sh ├── phpunit.xml ├── tests │ └── GoogleSearchTest.php ├── tmp ★コレ │   ├── 2021-03-13-06-43-19.jpg │   └── 2021-03-13-06-43-22.jpg └── vendor
Code language: plaintext (plaintext)

まず、tmpディレクトリを作成します。

$ mkdir tmp
Code language: Bash (bash)

次にスクリーンショット用のメソッドを作ります。

Pageクラスのscreenshotメソッドを使います。

キー内容
path画像ファイルの保存パス
fullPagefalse: ブラウザに表示されている範囲のスクリーンショット
true: 表示されていない範囲を含めたスクリーンショット

fullPage = trueは、注意が必要です。

内部的に、下へスクロールしながら、スクリーンショット画像を保存して、つなぎあわせているようです。

問題になりそうなのが、ページ下まで到達すると、クルクルを表示して、次のコンテンツを表示するページの場合です。ものすごく長いスクリーンショット画像になってしまうかもしれません。

別のケースとして、ページ上半分を表示しているだけでは走らないはずの処理が、スクリーンショットを保存すると、その処理が走ってしまうことがあるかもしれません。

class GoogleSearchTest extends TestCase { 〜省略function screenShot(): void { $path = __DIR__ . '/../tmp/' . strftime('%Y-%m-%d-%H-%M-%S.jpg'); $this->page->screenshot([ 'path' => $path, 'fullPage' => true, ]); } }
Code language: PHP (php)

さきほどのテストメソッドに、screenShot()をさしこみます。

public function test_GoogleでPuPHPeteer検索するとrialto_php_puphpeteerがあること(): void { $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->page->waitForNavigation("{waitUntil: ['load', 'networkidle2']}"); $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: PHP (php)

テストを実行すると、2枚、スクリーンショットが保存されました。

リファクタ

ベースクラス BasePuppeteerTestCaseクラス、GoogleSearchTestクラスに分けました。

GoogleSearchTestクラス

ninton/study_puphpeteer
study PuPHPeteer. Contribute to ninton/study_puphpeteer development by creating an account on GitHub.

BasePuppeteerTestCaseクラス

ninton/study_puphpeteer
study PuPHPeteer. Contribute to ninton/study_puphpeteer development by creating an account on GitHub.

つづく

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