selenium関連の諸々をダウンロードするライブラリを書いた

投稿日:

というわけで書きました。

https://github.com/shimabox/selenium-downloader

以下に、これを書いた経緯と使い方をずらずらと書いていきます。

なぜ書いたのか

一言で言えば、めんどくさいから。

自分はselenimu系で何か書こうとしたとき facebook/php-webdriver を利用するのですが(PHPで書けるからね)、selenium-server-standalone, ChromeDriver, geckodriver あげくのはてに IEDriverServer をダウンロードしなくちゃならないときがあるわけです。

それがも~う、めんどくさい。なので書きました。
(macにはbrewがあるって?まぁまぁ、そこは察してください)
(え?puppeteer?? まぁまぁ、そこは)

特徴

このライブラリは以下の特徴を持ちます。

  • selenium関連の諸々をターミナルから、インタラクティブ(対話形式)またはオプション指定でダウンロードできます
  • プラットフォームは、Mac, Windows, Linux から選択できます

インストール方法

composer

$ composer require shimabox/selenium-downloader

後述しますが composer require で落とした場合、autoloadの関係で1つファイルをかまさないといけません。

clone

$ git clone https://github.com/shimabox/selenium-downloader
$ cd selenium-downloader
$ composer install

自分のところでcomposer install|updateした場合、.env.defaultファイルをコピーして.envファイルを作成します。

設定(.env)

デフォルト値を.envファイルで管理しています。
なのでデフォルト値を変更したい場合、.envファイルを修正してください。
composer require で落とした場合、.envファイルは作成されないので同階層にある.env.defaultファイルをコピーして作るか、.env.defaultファイルを修正してください

// ファイルをダウンロードするディレクトリを指定します
DEFAULT_OUTPUT_DIR=
// デフォルトで落とす selenium-server-standalone のバージョンを指定します
// なんで3.8.1かというと、selenium起動時に -enablePassThrough false をつけないと色々動かないケースがあるからです
// 3.8.1まで、-enablePassThrough false が指定できます
DEFAULT_SELENIUM_VER='3.8.1'
// デフォルトで落とす ChromeDriver のバージョンを指定します
DEFAULT_CHROMEDRIVER_VER='2.43'
// デフォルトで落とす geckodriver のバージョンを指定します
DEFAULT_GECKODRIVER_VER='0.23.0'
// デフォルトで落とす IEDriverServer のバージョンを指定します
DEFAULT_IEDRIVER_VER='3.14.0'

使い方

対話形式でダウンロード

Demo
demo-interaction

この通り、引数無しで実行すれば対話が始まります。

$ php selenium_downloader.php

# "m" or "w" or "l" はどれかを入力してください。
Please select platform. [m]ac, [w]indows, [l]inux: w
# 出力するディレクトリパスを入力してください。
# デフォルトは.envで設定されているパスです。
# .envでも設定されていない場合、"selenium-downloader/xxx" に出力します。
Please enter the output directory
Default[/default/output/path]:
# selenium-server-standaloneが必要かどうか。
# デフォルトは No です。
Do you need Selenium? [N]o, [y]es: y
# selenium-server-standalone のバージョンを指定してください。
# デフォルトは 3.8.1 です。
Please enter selenium-server-standalone version Default [3.8.1]: 3.8.1
# ChromeDriverが必要かどうか。
Do you need ChromeDriver? [N]o, [y]es: y
# ChromeDriver のバージョンを指定してください。
# デフォルトは 2.43 です。
Please enter ChromeDriver version Default [2.43]:
# geckodriverが必要かどうか。
Do you need GeckoDriver? [N]o, [y]es: y
# geckodriver のバージョンを指定してください。
# デフォルトは 0.23.0 です。
Please enter GeckoDriver version Default [0.23.0]:
# IEDriverServerが必要かどうか。
Do you need IEDriver? [N]o, [y]es: y
# IEDriverServer のバージョンを指定してください。
# デフォルトは 3.14.0 です。
Please enter IEDriver version Default [3.14.0]:
# OSのBit数を指定してください。
# デフォルトは 32 です。
# なぜ32かというと、32bitバージョンのほうが64bitバージョンよりもキー入力が早いからです。
Please enter OS bit version [32]bit, [64]bit, Default[32]:
Done.

オプション指定でダウンロード

Demo
demo-option

以下のオプションが指定できます。

形式 説明
-h,—help ヘルプメッセージを出力して終了。
-p platformを [m]ac or [w]indows or [l]inux から選択します。
“-h, —help” が指定されていなければ必須となります。
-d 出力するディレクトリパスを入力します。
デフォルトは対話形式と一緒です。
-s selenium-server-standalone のバージョンを入力してください。
-c ChromeDriver のバージョンを入力してください。
-g geckodriver のバージョンを入力してください。
-i IEDriverServer のバージョンを入力してください。
-b OSのビット数を 32 か 64で入力してください。
デフォルトは “32” です(32か64以外が指定されていても)。

ヘルプの例。

$ php selenium_downloader.php -h
Usage:
  -h,--help
    Display help message and exit.
  -p platform (required)
    Select platform [m]ac or [w]indows or [l]inux.
    Required except that "--help, -h" is specified.
  -d output_dir_path
    Enter the output directory path.
    If not specified, it is output to the path specified by .env.dafault|.env(DEFAULT_OUTPUT_DIR).
    If there is still no value, it is output to "selenium-downloader/xxx".
  -s selenium-standalone-server_ver
    Enter the version of selenium-standalone-server. (e.g 3.8.1, 3.7(3.7.0)
    (Recommend version 3.8.1)
  -c ChromeDriver_ver
    Enter the version of ChromeDriver. (e.g 2.43
  -g geckodriver_ver
    Enter the version of GeckoDriver. (e.g 0.23(0.23.0), 0.20.1
  -i IEDriverServer_ver
    Enter the version of IEDriverServer. (e.g 3.14(3.14.0)
  -b bit_of_os
    Enter the number of OS bits (32 or 64).
    Default is "32" (Because key input is earlier than 64bit version).
    Valid only when IEDriverServer is specified.

e.g) 1 Basic.
$ php selenium_downloader.php -p m -s 3.8.1 -c 2.43 -g 0.23
e.g) 2 When specifying the output directory.
$ php selenium_downloader.php -p m -d /your/path/to -s 3.8.1
e.g) 3 When downloading the 64 bit version of the IEDriverServer.
$ php selenium_downloader.php -p w -i 3.14.0 -b 64
e.g) 4 When downloading only geckodriver.
$ php selenium_downloader.php -p m -g 0.23
or
$ php selenium_downloader.php -p m -s "" -c "" -g 0.23

自動でダウンロード

以下のように Optionable インターフェイスを実装したクラスを用意して、SMB\SeleniumDownloader\Downloader に渡してあげると自動でダウンロード処理を行うことができます。

<?php
require_once 'vendor/autoload.php';

use SMB\SeleniumDownloader\Downloader;
// Interface
use SMB\SeleniumDownloader\Argument\Optionable;

// Prepare a class that implements the optionable interface.
class InstantSelenium implements Optionable
{
    /**
     * Returns true if option is specified.
     *
     * @return boolean
     */
    public function isSpecified()
    {
        return true;
    }

    /**
     * If true it will output a help message.
     *
     * @return boolean
     */
    public function isSpecifiedHelp()
    {
        return false;
    }

    /**
     * Create help message.
     *
     * @return string
     */
    public function createHelpMessage()
    {
        return '';
    }

    /**
     * Get optional arguments.
     *
     * e.g)
     * <code>
     * return [
     *     'p' => 'w',      // Select platform [m]ac or [w]indows or [l]inux.
     *     'd' => '.',      // Enter the output directory path.
     *     's' => '3.8.1',  // Enter the version of selenium-standalone-server. (e.g 3.8.1, 3.7(3.7.0)
     *     'c' => '2.43',   // Enter the version of ChromeDriver. (e.g 2.43
     *     'g' => '0.23.0', // Enter the version of GeckoDriver. (e.g 0.23(0.23.0), 0.20.1
     *     'i' => '3.14.0', // Enter the version of IEDriverServer. (e.g 3.14(3.14.0)
     *     'b' => '32',     // Enter the number of OS bits (32 or 64).
     * ];
     * </code>
     *
     * @return array
     */
    public function get()
    {
        return [
            'p' => 'm', // Select mac.
            's' => '3.8.1',
            'c' => '2.43',
            'g' => '0.23.0',
        ];
    }
}

$downloader = new Downloader(new InstantSelenium());
$downloader->execute();

instant_selenium.php とします

上記のDemo
demo-instant

試してみる (GoogleChromeとFirefoxのヘッドレスモード)

以下条件で、Googleにアクセスするテストを試してみます。

  • GoogleChromeとFirefoxのヘッドレスモード
  • iPhone, iOS12, Safari のユーザーエージェント
  • 414 x 736 の画面サイズ

事前準備

shimabox/selenium-downloader をインストール。

$ composer require shimabox/selenium-downloader

facebook/webdriver をインストール。

$ composer require facebook/webdriver

以下ファイルを作成します。

  • selenium_downloader.php
<?php
require_once 'vendor/autoload.php';
require_once 'vendor/shimabox/selenium-downloader/selenium_downloader.php';

composer require で落とした場合、vendor/autoload.php を読み込まないと直接呼べないのでこうする必要があります。

  • example_chrome.php
<?php
require_once 'vendor/autoload.php';

use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;

/*
 |------------------------------------------------------------------------------
 | ChromeOptions
 |------------------------------------------------------------------------------
 */
$options = new ChromeOptions();

// headless
$options->addArguments([
    '--headless',
    '--no-sandbox',
    '--disable-dev-shm-usage',
]);

// change window size
// set window-size=width,height
$width = 414;
$height = 736;
$options->addArguments(["window-size={$width},{$height}"]);

// UserAgent iOS 12.0
$ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1';
$options->addArguments(['--user-agent=' . $ua]);

/*
 |------------------------------------------------------------------------------
 | DesiredCapabilities
 |------------------------------------------------------------------------------
 */
$capabilities = DesiredCapabilities::chrome();

/*
 |------------------------------------------------------------------------------
 | Set ChromeOptions to DesiredCapabilities
 |------------------------------------------------------------------------------
 */
$capabilities->setCapability(ChromeOptions::CAPABILITY, $options);

/*
 |------------------------------------------------------------------------------
 | Create WebDriver
 |------------------------------------------------------------------------------
 */
$driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities);

/*
 |------------------------------------------------------------------------------
 | test
 |------------------------------------------------------------------------------
 */

// URL
$url = 'https://www.google.com/webhp?gl=us&hl=en&gws_rd=cr';

// 指定URLへ遷移 (Google)
$driver->get($url);

// 検索Box
$findElement = $driver->findElement(WebDriverBy::name('q'));
// 検索Boxにキーワードを入力して
$findElement->sendKeys('Hello');
// 検索実行
$findElement->submit();

// 検索結果画面のタイトルが 'Hello - Google Search' になるまで10秒間待機する
// 指定したタイトルにならずに10秒以上経ったら
// 'Facebook\WebDriver\Exception\TimeOutException' がthrowされる
$driver->wait(10)->until(
    WebDriverExpectedCondition::titleIs('Hello - Google Search')
);

// Hello - Google Search というタイトルが取得できることを確認する
if ($driver->getTitle() !== 'Hello - Google Search') {
    throw new Exception('fail');
}

echo $driver->getTitle();

// ブラウザを閉じる
$driver->close();
  • example_firefox.php
<?php
require_once 'vendor/autoload.php';

use Facebook\WebDriver\Firefox\FirefoxDriver;
use Facebook\WebDriver\Firefox\FirefoxProfile;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverDimension;
use Facebook\WebDriver\WebDriverExpectedCondition;

/*
 |------------------------------------------------------------------------------
 | FirefoxProfile
 |------------------------------------------------------------------------------
 */
$profile = new FirefoxProfile();

// UserAgent iOS 12.0
$ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1';
$profile->setPreference('general.useragent.override', $ua);

/*
 |------------------------------------------------------------------------------
 | DesiredCapabilities
 |------------------------------------------------------------------------------
 */
$capabilities = DesiredCapabilities::firefox();

// headless
$capabilities->setCapability('moz:firefoxOptions' , ['args' => '-headless']);

/*
 |------------------------------------------------------------------------------
 | Set FirefoxProfile to DesiredCapabilities
 |------------------------------------------------------------------------------
 */
$capabilities->setCapability(FirefoxDriver::PROFILE, $profile);

/*
 |------------------------------------------------------------------------------
 | Create WebDriver
 |------------------------------------------------------------------------------
 */
$driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities);

// change window size
$width = 414;
$height = 736;
$dimension = new WebDriverDimension($width, $height);
$driver->manage()->window()->setSize($dimension);

/*
 |------------------------------------------------------------------------------
 | test
 |------------------------------------------------------------------------------
 */

// URL
$url = 'https://www.google.com/webhp?gl=us&hl=en&gws_rd=cr';

// 指定URLへ遷移 (Google)
$driver->get($url);

// 検索Box
$findElement = $driver->findElement(WebDriverBy::name('q'));
// 検索Boxにキーワードを入力して
$findElement->sendKeys('Hello');
// 検索実行
$findElement->submit();

// 検索結果画面のタイトルが 'Hello - Google Search' になるまで10秒間待機する
// 指定したタイトルにならずに10秒以上経ったら
// 'Facebook\WebDriver\Exception\TimeOutException' がthrowされる
$driver->wait(10)->until(
    WebDriverExpectedCondition::titleIs('Hello - Google Search')
);

// Hello - Google Search というタイトルが取得できることを確認する
if ($driver->getTitle() !== 'Hello - Google Search') {
    throw new Exception('fail');
}

echo $driver->getTitle();

// ブラウザを閉じる
$driver->close();

ダウンロード

  • mac を選択
  • -d . でカレントディレクトリにダウンロード指定
  • selenium-server-standaloneのバージョン 3.8.1 を指定
  • ChromeDriverのバージョン 2.43 を指定
  • geckodriverのバージョン 0.23.0 を指定
$ php selenium_downloader.php -p m -d . -s 3.8.1 -c 2.43 -g 0.23.0

seleniumの立ち上げ

$ java -Dwebdriver.chrome.driver=chromedriver -Dwebdriver.gecko.driver=geckodriver -jar selenium-server-standalone-3.8.1.jar -enablePassThrough false

※ geckodriverを使う場合は、-enablePassThrough false を付与してください
-D は各種driverのパスが通っていれば付与する必要はありません

実行

// chrome
$ php example_chrome.php
// firefox
$ php example_firefox.php

ターミナルに

Hello - Google Search

と出ていれば成功です。
※ 画面サイズを変えているところ(change window size)や、UAをセットしているところ(UserAgent iOS 12.0)、ヘッドレスモードを指定しているところ(headless)を弄って試してみてください

工夫したところ

ソースを見てもらうとわかりますが、CUIでの対話やオプション指定などコンテキストに依存する処理で構成されています。
つまり、単純に書くとテストを書くことがかなり困難になります。

そんなときは

  • 依存するオブジェクトのメッセージ(関数)をインターフェイスに切り出す
    • 抽象化
  • それを必要としているオブジェクトに渡してあげる
    • 依存性の注入
  • オブジェクトはそのインターフェイスと会話するようにする

とすると解決できることが多いです。

CUIでの対話は (GitHub – thephpleague/climate: PHP’s best friend for the terminal. ) を使っているのですが ※超便利、この子の処理を肩代わりするオブジェクト(Proxy)を作って解決を図りました。
※ これProxyなのかAdapterなのか今も悩んでる

オプション指定は、Optionable インターフェイスを作って解決です。 ※ 名前が微妙だけど
これがあることによって、上であげた 自動でダウンロード みたいなことが出来るようになります。

これがBestだとは思いませんが、少なくともテストは書きやすくなったと思います。
(テスト書いてないけどな)

※ テスト書きました

Add test by shimabox · Pull Request #1 · shimabox/selenium-downloader

雑な設計

おわりに

いかがでしたでしょうか、かなり簡単、、、?じゃないにしろイメージは掴んでいただけたかと思います。
テスト用スクリプトが作成できていれば、バージョン違いのseleniumや各種driverでの確認はサクッとできるのではないでしょうか。
(以前はうまく動く組み合わせを見つけるのがめんどくさかったのですが、現状 selenium-server-standaloneのバージョン 3.8.1 であればけっこううまく動いてくれます)

こういった類は準備がめんどくさいというイメージが強いのでちょっとは楽になった気がします。
めんどくさいものは自動化するっていうのが漢ってやつです。

せっかくなので、GitHub – shimabox/screru: Screru is a library that supplements php-webdriver にも組み込んでみようかと思います。

↑こちら組み込みました。
php-webdriverのラッパーライブラリ”screru”のバージョンをあげた – Shimabox Blog

それでは。

作成者: shimabox

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

コメントする

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

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