uzullaがブログ

uzullaがブログです。

PHPerのゆるふわSelenium

某所某日

お客様「この画面遷移が〜(延々)」俺「はい…」お客様「あっ、そういえばこの画面でこれ追加」俺「はい…」

俺「これ画面遷移マジ複雑だし、仕様変更の量半端ないし、これは後でエンバグするやろ…。自動テストしたい…、でもTwitter連携あるからなー。PHPだし…ちゃんとしたMechanizeみたいなのないし*1seleniumしかないか…」

Seleniumやる前のイメージ

・なんかすごい面倒くさい
・なんか色々面倒くさい
・なんかめっちゃ面倒くさい

   ↓

・しかたない、もうPerlでテスト書こうかな…

やった後のイメージ

・複雑なことしなければ超カンタンじゃねーか!だまされた
PHPで十分や!!!

手順

・SeleniumServerをいれる
PHPUnitで動くSeleniumのライブラリ入れる
Seleniumのテストケースを生成するツールをいれる
・テストケース作る
・手元のMacでうごかす

Seleniumを入れる

http://code.google.com/p/selenium/downloads/list
から、selenium-server-standalone-2.33.0.jarをDLする


特にインストールに伴う細かい作業や起動オプションは必要なく、`java -jar selenium-server-standalone-2.33.0.jar` であっさり起動する。

phpunit+ PHPUnit_Extensions_SeleniumTestCaseを入れる

まあ、いっぱんてきなうぇぶえんじにあなら最近のPHP 5.5が入ってるでしょうから、

curl -s http://getcomposer.org/installer | php

この一行でComposerをいれた上で、

あとはcomposer.jsonを作って

{
    "require": {
        "phpunit/phpunit": "3.7.*",
	"phpunit/phpunit-selenium": "1.3.*"
    }
}

あとはphp composer.phar install 一発で完了です!簡単ですね!!


(composerは、Perlでいうところのcartonみたいなやつです)

テストケースを作成する

selenium builderでひな形作成

必須ではないんですけど、テストケースを手で書くのは結構だるい感じなので、今回selenium builderを使います、FirefoxのAddonです。
http://sebuilder.github.io/se-builder/
Firefox右下に追加される緑ブロックをクリックするとselenium builderのウインドウが立ち上がります。

ここのSelenium1を選択して*2、レコード開始です。
f:id:uzulla:20130714004729p:plain
・画面をクリックしたり、入力したり
・"Record a Verifycation" を押してから、チェックしたい画面要素をクリック
・間違えたらルール消したり

とかやってると、ルールがザクザクとできていきます。
正直立派なUIではないのですけど、まあできていきます。

とりあえずローカルテスト

Selenium Builderウインドウのプルダウンの「run > run test locally」を実行すると、実行が確認できます。

うまく走りますかね?Twitter連携アプリとかだと、ルール作成が結構罠あるので後述します。

phpUnit形式での書き出し

問題なければプルダウンメニューから、「File > save」を選択して 「PHP - selenium RC」 を押すと、ファイル保存ダイアログが出るので、Example.phpとネームつけて保存、これがひな形になります。

テストケースを実行する

少し修正

生成されたPHPはひな形なので、保存したphpファイルをひらいて修正します。

今回composerつかったので、最初のローダ行を

require_once "vendor/autoload.php";

などと修正し、

$this->setBrowser('*chrome');

をとりあえず「*firefox」に修正します。

そうすると

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

class Example extends PHPUnit_Extensions_SeleniumTestCase
{
    protected function setUp()
    {
        $this->setBrowserUrl('http://www.google.com');
        $this->setBrowser('*firefox');
    }

    public function myTest()
    {
        $this->open("/");
        $this->waitForPageToLoad("60000");
        try {
            $this->assertTrue($this->isTextPresent("google"));
        } catch (PHPUnit_Framework_AssertionFailedError $e) {
            array_push($this->verificationErrors, $e->toString());
        }
    }
}

こんな感じになりますかね。

実行してみる

$ vendor/bin/phpunit Example
PHPUnit 3.7.22 by Sebastian Bergmann.

...

Time: 0 seconds, Memory: 5.50Mb

OK (1 tests, 1 assertions)
$

やった!うごいた!あとは書き足していきましょう!


うごかなかった人、特にSeleniumが「error: Unexpected Exception: Value does not implement interface Event.」とか言う人もいたと思います、というか、今現在最新版のFirefoxを使っていると、言われると思います…。

落とし穴

まあ、一発でうごいたら本当にラッキーですね、いくつか落とし穴がありました。私がハマった穴を紹介します

落とし穴0:Seleniumさんマジせっかち

リンクやボタンを押してページ遷移をさせて、その次のページに要素があるか評価したい、というよくあるケースをつくるとき に、ページロードを待たないで評価してエラーになる事がよくあります。

いわゆるSelenese*3の clickAndWaitにならないで、Clickの次にassertがはいるようなケースをSelenium Builderは生成しがちなので、ここはだるいけど手で修正しましょう。

落とし穴1:Firefox22(最新版)でうごかない

http://code.google.com/p/selenium/issues/detail?id=5841
どうも、最新版のFirefox22では前述の「error: Unexpected Exception: Value does not implement interface Event.」というエラーが出ます。


まずは過去バージョン配布サイト( https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/ ) Firefox 21をDLして、dmgの中のFirefox.appをFirefox_21.appなどとリネームしてどこかに保存し、

$this->setBrowser('*firefox /path/to/Firefox_21.app/Contents/MacOS/firefox');

とか適当に指定すれば、好きな場所のバイナリのFirefoxを使えます。
多分これはselenium-serverのバグなので、そのうちなおると思いますが、だるいですね。

落とし穴2: セッション全部切れる

Seleniumで起動されるブラウザは「素」のものが起動します。Twitter連携アプリ等のログインがあるものののテストをするときは、普段のブラウザでルールをつくると、ログインのフェーズがスッポ抜けてエラーになることがあります。

全部ログアウトしてからルールを作りましょう。
(このあたり、SeleniumBuilderじゃなくて、SeleniumIDEでやると気づきやすいんですよね…)

落とし穴3: Seleniumbuilderはヘボい

一発で良い感じのテストケースができるのは稀な感じあります。
最終的には、生成されたコードをちまちまと書き直し、再実行、書き直し、再実行、とするしかない。
まあ単純な操作ならいいんですけど、途中にリダイレクトがはいるようなサイトだと割りと失敗したりする。しょーーじきダルいですね。


ちなみに、Selenium IDE( http://docs.seleniumhq.org/projects/ide/ ) はルール作成機能が高機能で、使ってみるとBuilderよりもテストケース作成がさくさくできます。
特にステップ実行や、Assertの即時実行などができるのでなんだかんだこちらのほうが楽…。

しかしながら、SeleniumIDEはSelenium 2(Webdriver)向けにつくられているので、Selenium1(RC)むけのPHPテストケース出力とかできないのですよね、辛い。


ただまあ、SeleniumIDE は Javaのひな形なら 1(RC)等へのエクスポートができるので、
それを手動変換って感じかなと思います…ちょっと複雑なテストだと、遠回りのようで、こっちのほうがはやいかも。


(1(RC)と2(Webdriver)は文法が違うので注意)

(まあ、つかって間もない私の感想ですけど、最終的には全部ルール手でかきそうです…)

selenium_1_(RC)と selenium_2_(Webdriver)の話

今回入れたselenium-serverはSelenium2のものなんですが、バックワードコンパチで1(RC)のAPIをサポートしているそうです。

今回つかったPHPUnit_Extensions_SeleniumTestCaseは、1(RC)のAPIで叩くものです。なので、最新のIDEは1をもうサポートしないので、書き出せない、という感じらしいです。


一応PHPにも PHPUnit_Extensions_Selenium2TestCaseというのがあるのですが、一部機能が実装されていないらしいし、IDEもBuilderも対応していないっぽいので、使う意義が私にはみいだせませんでした…

しかも、PHPUnit_Extensions_Selenium2TestCaseはComposerでパッって一発ではいらないっぽいんですよね…。


まあ、いつか2(Webdriver)にせざるをえないのかもしれませんが、しばらくはSeleniumServerは1も2もサポートするっぽいので、当面1(RC)で書こうと思います…。

まとめ

変な所でひっかからなければ、ローカルマシンでのSeleniumのテストは1時間もかからず書き始められます。

PHPはMechanizeがないとか生きるのが辛めと言われますが、SeleniumさえあればTwitterとやたらと連携するようなサービスでも気軽に自動テストできるっぽいのではかどりますね!!

サァ!みんなPHPを書こう!

おまけとお役立ち情報

chromeでテストしたい

http://code.google.com/p/chromedriver/downloads/list

から chromedriver_mac32_2.1.zip などをDLして、でてきたバイナリコマンドをPathが通ってる所に置けばOKです、簡単ですね。

エッ… Perl…?

まあ、私の知り合い大体Perlの人ですからね、しかたないですね…。
(っていうか、このエントリ自体が昨日やったHachioji.pm #30 Takao.mtのLT資料の再編です)
めんどいのではしょりますけど、selenium builderはPerl対応もしてるらしいっすよ?試してないから、しらないけど。

エッ、コード?

#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use Selenium::Remote::Driver;
use Test::More;

my $driver = Selenium::Remote::Driver->new(
    browser_name => 'firefox',
);

{
    $driver->get('http://www.google.com');
    my $title = $driver->get_title;
    cmp_ok($title, 'eq', 'Google', 'Test: $title eq "Google"');
}

$driver->quit;
$ perl ./test.pl 
ok 1 - Test: $title eq "Google"
# Tests were run but no plan was declared and done_testing() was not seen.

ハイハイ!Perlでもちゃんとうごきますよ!しかもまじ手軽だし、Selenium 2にも対応してるっぽいよ!!!
なんか色々できるしMechanizeのかわりにもつかえそうだね!よかったね!ああよかったね!

PHP仲間を募集しています

是非Hachioji.pmにあそびにきてください、主催だけPHPerで泣きそうです。
http://hachiojipm.org/

*1:あるの?

*2:後述しますけど、ここは1で

*3:Seleniumのコマンド群