ちょっと前になりますが、画面にあるリンクのURL(aタグのhrefの値)を全部なめて、期待するURLになっているかどうかをテストしなくてはならないお仕事がありまして、これは人間のやる仕事ではない!これは機械にやらせる仕事だ!とついカッとなってSelenium関連を調べていたら以下の素晴らしいOSSに巡り逢いました。
※ え、いまさら?とか言わないで
facebook/php-webdriver: A php client for webdriver.
これは
- Facebook様が作っておられる
- Selenium WebDriverをPHPでラップしているのでPHPからWebブラウザを制御できる
と、PHPerには嬉しいものとなっております。
私はこれで気の狂いそうなテストを機械に行なってもらうことに成功しました。
※ テストを書くのはもちろん大変なんだけど
案件において、上記のテストは仮想環境のCentOS上で行ったのですが、家のMacでもphp-webdriverを軽く動かしてみたので今回はその備忘録になります。
環境
環境ですが、
- macOS Sierra 10.12.4
- php
- 5.6.30 (cli) (built: Feb 7 2017 16:06:52)
php-webdriver
は PHPのバージョンが5.5 以上であれば大丈夫です
- java jdk
- 1.8.0_77
- javaは 1.8 以上であれば大丈夫です
自分はこんな感じです。
とりあえず、PHP(>=5.5) と Java(>=1.8) が入っていればよいと思います。
利用するブラウザ
利用するブラウザのバージョンは以下の通りです。
- Chrome
- 58.0.3029.81 (64-bit)
- Firefox
- 53.0 (64 ビット)
php-webdriverを実行するために必要なもの
まずphp-webdriverを実行するために、以下が必要になりますので
- selenium-server-standalone
- v3.4.0
- geckodriver
- v0.16.1
- chromedriver
- v2.29
これらをそれぞれダウンロードします。
※ バージョンはこの記事を書いた時点(2017/04/30)でのものになりますので適宜確認してください
※ firefoxだけのテストなら、geckodriverだけでいいし、chromeだけのテストならchromedriverだけでもいいです
selenium-server-standalone のダウンロード
http://selenium-release.storage.googleapis.com/index.html?path=3.4/ から
selenium-server-standalone-3.4.0.jar をダウンロードします。
geckodriver のダウンロード
Firefox47以降は、FirefoxDriverではなくgeckodriverを使用するようです。
Release v0.16.1 · mozilla/geckodriver から geckodriver-v0.16.1-macos.tar.gz をダウンロードして解凍します。
注意点
上記でダウンロードしたselenium-server-standalone-3.4.0では、v0.16.1のgeckodriverでないと、実行後にブラウザが閉じなかったり、こんなエラー Unable to connect to host 127.0.0.1 on port 7055 after 45000 ms. Firefox console output
が出るので、seleniumのバージョンとgeckodriverのバージョンの組合せは気をつけたほうがいいです。
解凍すると、geckodriver
という実行ファイルが出来るので、以下の様にパスを通して実行権限も付与しておきます。
$ mv geckodriver /usr/local/bin/ $ chmod +x /usr/local/bin/geckodriver
chromedriver のダウンロード
https://chromedriver.storage.googleapis.com/index.html?path=2.29/ から https://chromedriver.storage.googleapis.com/2.29/chromedriver_mac64.zip をダウンロードして解凍します。
解凍すると、chromedriver
という実行ファイルが出来るので、以下の様にパスを通して実行権限も付与しておきます。
$ mv chromedriver /usr/local/bin/ $ chmod +x /usr/local/bin/chromedriver
php-webdriverのインストール
ここまでで、実行環境は整ったはずなのでphp-webdriver
をインストールします。
といっても、Composer
で一発です。
※ Composerって何?という人はググってみてください
$ composer require facebook/webdriver
これでおkです。
selenium-server-standalone を起動する
あとはプログラムを書いていくのですが、その前にSeleniumを起動させておきます。
$ java -jar selenium-server-standalone-3.4.0.jar &
※ selenium-server-standalone-3.4.0.jar は最初にダウンロードしたjarファイルを指定してください
※ selenium-server-standalone の停止方法
上記のコマンドでは &
をつけてバックグラウンドで実行しているのですが、それだとkillする時がめんどくさいので以下のコマンドを打ちます。
※ バックグラウンドで実行しなければ、普通にCtrl + c
で停止できます
$ ps aux | grep [s]elenium-server-standalone | awk '{ print "kill -9", $2 }' | sh
サンプル1
こちらがまず軽く動かしてみたサンプルになります。
Googleに遷移して、適当に検索をして、タイトルが変わるのを待ってからキャプチャを撮ります。
これをChromeとFirefoxのブラウザで行わせてみます。
sample_1.php
<?php require_once './vendor/autoload.php'; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverBy; /** * selenium php-webdriver 実行のサンプル * @param string $browser chrome or firefox */ function sample_1 ($browser) { // selenium $host = 'http://localhost:4444/wd/hub'; switch ($browser) { case 'chrome': // chrome ドライバーの起動 $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); break; case 'firefox': // firefox ドライバーの起動 $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); break; } // 画面サイズをMAXに $driver->manage()->window()->maximize(); // 指定URLへ遷移 (Google) $driver->get('https://www.google.co.jp/'); // 検索Box $element = $driver->findElement(WebDriverBy::name('q')); // 検索Boxにキーワードを入力して $element->sendKeys('GWの予定'); // 検索実行 $element->submit(); // 検索結果画面のタイトルが 'GWの予定 - Google 検索' になるまで10秒間待機する // 指定したタイトルにならずに10秒以上経ったら // 'Facebook\WebDriver\Exception\TimeOutException' がthrowされる $driver->wait(10)->until( WebDriverExpectedCondition::titleIs('GWの予定 - Google 検索') ); // GWの予定 - Google 検索 というタイトルを取得できることを確認する if ($driver->getTitle() !== 'GWの予定 - Google 検索') { throw new Exception('fail'); } // キャプチャ $file = __DIR__ . '/' . __METHOD__ . "_{$browser}.png"; $driver->takeScreenshot($file); // ブラウザを閉じる $driver->close(); } // chrome sample_1('chrome'); // firefox sample_1('firefox');
実行してみると、以下のGifの様にブラウザが勝手に立ち上がって、勝手に操作されているのが分かるかと思います。
Seleniumって今更感ありますけど、この光景を見ると未だにちょっと感動します。
キャプチャもどうでしょうか。画面全体ではないですが(これどうにかならないかな。。)、保存されていると思います。
サンプル2 画面サイズを変えてみる
サンプル1で簡単にChromeとFirefoxを操作することが出来ました。
では、レスポンシブ対応を行っているようなサイトの確認はどうしたらいいでしょうか。
画面サイズを好きに変更したいところです。
はいこうします。
sample_2.php
<?php require_once './vendor/autoload.php'; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; // add /** * selenium php-webdriver 実行のサンプル * @param string $browser chrome or firefox * @param array $size ['w' => xxx, 'h' => xxx] */ function sample_2 ($browser, array $size) { // selenium $host = 'http://localhost:4444/wd/hub'; switch ($browser) { case 'chrome': // chrome ドライバーの起動 $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); break; case 'firefox': // firefox ドライバーの起動 $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); break; } // 画面サイズをMAXに $driver->manage()->window()->maximize(); if (isset($size['w']) && isset($size['h'])) { // サイズを指定 $dimension = new WebDriverDimension($size['w'], $size['h']); $driver->manage()->window()->setSize($dimension); } // 指定URLへ遷移 (Google) $driver->get('https://www.google.co.jp/'); // 検索Box $element = $driver->findElement(WebDriverBy::name('q')); // 検索Boxにキーワードを入力して $element->sendKeys('GWの予定'); // 検索実行 $element->submit(); // 検索結果画面のタイトルが 'GWの予定 - Google 検索' になるまで10秒間待機する // 指定したタイトルにならずに10秒以上経ったら // 'Facebook\WebDriver\Exception\TimeOutException' がthrowされる $driver->wait(10)->until( WebDriverExpectedCondition::titleIs('GWの予定 - Google 検索') ); // GWの予定 - Google 検索 というタイトルを取得できることを確認する if ($driver->getTitle() !== 'GWの予定 - Google 検索') { throw new Exception('fail'); } // キャプチャ $file = __DIR__ . '/' . __METHOD__ . "_{$browser}.png"; $driver->takeScreenshot($file); // ブラウザを閉じる $driver->close(); } // iPhone6のサイズ $size4iPhone6 = ['w' => 375, 'h' => 667]; // chrome sample_2('chrome', $size4iPhone6); // firefox sample_2('firefox', $size4iPhone6);
こんな感じで、WebDriverDimension
を使うと上手くいきます。
サンプル3 UAを偽装してみる
画面サイズの変更は出来たのでレスポンシブ対応しているサイトの動作確認は取れそうです。
では、レスポンシブ対応ではなく、UserAgentでSPのViewを出し分けているようなサイトの確認はどうしたらいいでしょうか。
UserAgentを偽装したいところです。
はいこうします。
sample_3.php
<?php require_once './vendor/autoload.php'; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\Chrome; // add for chrome use Facebook\WebDriver\Firefox; // add for firefox /** * selenium php-webdriver 実行のサンプル * @param string $browser chrome or firefox * @param array $size ['w' => xxx, 'h' => xxx] * @param string overrideUA true : override UserAgent */ function sample_3 ($browser, array $size = [], $overrideUA = '') { // selenium $host = 'http://localhost:4444/wd/hub'; switch ($browser) { case 'chrome': // chrome $cap = DesiredCapabilities::chrome(); if ($overrideUA === '') { break; } $options = new Chrome\ChromeOptions(); $options->addArguments(['--user-agent=' . $overrideUA]); $cap->setCapability(Chrome\ChromeOptions::CAPABILITY, $options); break; case 'firefox': // firefox $cap = DesiredCapabilities::firefox(); if ($overrideUA === '') { break; } // iPhone(iOS10) のUAをセット $profile = new Firefox\FirefoxProfile(); $profile->setPreference('general.useragent.override', $overrideUA); $cap->setCapability(Firefox\FirefoxDriver::PROFILE, $profile); break; } // ドライバーの起動 $driver = RemoteWebDriver::create($host, $cap); // 画面サイズをMAXに $driver->manage()->window()->maximize(); if (isset($size['w']) && isset($size['h'])) { // サイズを指定 $dimension = new WebDriverDimension($size['w'], $size['h']); $driver->manage()->window()->setSize($dimension); } // 指定URLへ遷移 (Google) $driver->get('https://www.google.co.jp/'); // 検索Box $element = $driver->findElement(WebDriverBy::name('q')); // 検索Boxにキーワードを入力して $element->sendKeys('GWの予定'); // 検索実行 $element->submit(); // 検索結果画面のタイトルが 'GWの予定 - Google 検索' になるまで10秒間待機する // 指定したタイトルにならずに10秒以上経ったら // 'Facebook\WebDriver\Exception\TimeOutException' がthrowされる $driver->wait(10)->until( WebDriverExpectedCondition::titleIs('GWの予定 - Google 検索') ); // GWの予定 - Google 検索 というタイトルを取得できることを確認する if ($driver->getTitle() !== 'GWの予定 - Google 検索') { throw new Exception('fail'); } // キャプチャ $file = __DIR__ . '/' . __METHOD__ . "_{$browser}.png"; $driver->takeScreenshot($file); // ブラウザを閉じる $driver->close(); } // iPhone6のサイズ $size4iPhone6 = ['w' => 375, 'h' => 667]; // iOS10のUA $ua4iOS = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1'; // chrome sample_3('chrome', $size4iPhone6, $ua4iOS); // firefox sample_3('firefox', $size4iPhone6, $ua4iOS);
こんな感じで、chromeはChrome\ChromeOptions
を、firefoxはFirefox\FirefoxProfile
を使うと上手くいきます。
2017/05/02 追記
上記サンプルとPHPUnitを使ったテストのサンプルをGitHubにあげました。
shimabox/sample-phpwebdriver: Test with phpunit and phpwebdriver
課題
上記サンプルでスクリーンキャプチャを撮っていますが、画面全体のキャプチャが撮れていません。このへんはググってみると解決方法がちらほらあるようなので後日調べてみます。
まとめ
今回は簡単なサンプルしか書けませんでしたが、ブラウザ上での色々な動作を結構直感的に書けるので、今まで敬遠しがちだったE2Eのテストが捗るんではないかなと思いました。
でもE2Eって最初は書けるけど、それをどうやって保守していくかが鬼門だったりするんですよね。。
とはいえ、最初の一歩が大事だと思うので、これをキッカケにE2Eのテストも(必要な箇所を見極めつつ)きちんと書いていきたいなぁというところです。
おまけ
冒頭に書いた画面にあるリンクのURL(aタグのhrefの値)を全部なめて、期待するURLになっているかどうかの確認は大体こんな感じで書きました。
function test($url) { $host = 'http://localhost:4444/wd/hub'; $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); // 指定URLへ遷移 $driver->get($url); // 非同期で読み込んでいる要素とかあれば // こんな感じで要素が現れるまで待つ /* $driver->wait(10)->until( WebDriverExpectedCondition::presenceOfAllElementsLocatedBy( WebDriverBy::cssSelector('xxx') ) ); */ $elements = $driver->findElements(WebDriverBy::cssSelector('a')); foreach ($elements as $ele) { try { $href = $ele->getAttribute("href"); // $href のアサーション } catch (\Exception $e) { // ログに残しておくね $log->warning('参照エラー;' . $url); $log->warning($e->getMessage()); continue; } } $driver->close(); }
なんで例外をキャッチしているかというと、たまに以下のエラーを吐くことがあったからです。
(大量のURLをぐるぐる回したときとか)
Window not found. The browser window may have been closed.
とか
Error communicating with the remote browser. It may have died.
まぁ、このへんはログに残しておいて後で確認するという割り切りも大事です。
参考にさせて頂きました
- Home · facebook/php-webdriver Wiki
- HowTo Wait · facebook/php-webdriver Wiki
- Cheat sheet for using php webdriver (facebook/webdriver).
- Firefox 48以降でselenium-webdriverが動作しない問題とその回避策 – Qiita
- Selenium WebDriver ”Unable to connect to host” エラー – Qiita
- 【selenium】facebook/php-webdriverでUser Agentを変更する方法メモ – とりあえずphpとか
- webdriver – Selenium 2 – Setting user agent for IE and Chrome – Stack Overflow
上記内容で動作しないケースがあったら
こちらの記事を参考にして頂けると解決するかもしれません。
php-webdriverのラッパーライブラリ”screru”のバージョンをあげた – Shimabox Blog