php-webdriverをmacのローカルで試す

投稿日:

ちょっと前になりますが、画面にあるリンクの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.

まぁ、このへんはログに残しておいて後で確認するという割り切りも大事です。

参考にさせて頂きました

上記内容で動作しないケースがあったら

こちらの記事を参考にして頂けると解決するかもしれません。
php-webdriverのラッパーライブラリ”screru”のバージョンをあげた – Shimabox Blog

作成者: shimabox

Web系のプログラマをやっています。 なるべく楽しく生きていきたい。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください