Selenium の新しいコマンドを作る

Selenium には標準で素晴らしいコマンドが沢山存在している。


なので基本的にはそのコマンドを組み合わせて、
テストを行っていけば良い。


が、当然、標準のコマンドだけでは、
「〜のテストできんー」ってのはある。


つい最近仕事であったのは、
「ある画面にエラーメッセージが出るのだが、
同じメッセージが2個出ることを確認しないといけない。
それを自動化するにはどうしたらいいか?」
ってものだった。


Selenium の標準のコマンドには、
assertTextPresent というコマンドがある。
このコマンドは、html 形式だと

<tr>
    <td>assertTextPresent</td>
    <td>hoge</td>
    <td></td>
</tr>


という形で記述し、
第一引数の「hoge」がページ内に存在するか assert (検証)し、
存在しなかったらテストに失敗した状態にするというものである。


hoge」という文字列がページ内に2回現れることを、
この assertTextPresent では検証できない。
1個だけ存在しても、2個存在しても、3個存在してもエラーにはならないからだ。


というわけで、「hoge」がページ内に2個だけ存在することを確かめるためのコマンドを作った。
(Selenium Recoder をプロジェクトで使ってるので、
Selenium のバージョン0.6 だけで動作確認しました。
多分、Selenium 0.7 でも動くと思いますが)

/* 
 * assertTextPresentCount 
 */ 
Selenium.prototype.assertTextPresentCount = function(expectedTextReg, count) { 
    var allText = this.page().bodyText(); 
     
    if (allText.match(expectedTextReg, "g").length != count) { 
        Assert.fail("Page text not adapt"); 
    } 
};


Selenium.prototype はおまじないと思ってくれ(乱暴w)。
俺のつたない JavaScript の知識から言うと、
Selenium クラスのオブジェクトの prototype フィールドに新しいメソッドを付け加えてる。多分。
この辺りは、JavaScript だなーって感じで、
JavaScript のプロトタイプベースのオブジェクト指向
動的なメソッド、フィールドの追加の概念を理解してないと
「ハァ???」って感じのコードだろうけど。
まぁ、こういう形でかけばいいと覚えればよいと思ふ。
俺もよくわかってないしwww


このコード書くにあたっては、
Selenium のソースの selenium-api.js の assertTextPresent のコードを参照した。

/*
 * Asserts that the specified text is present in the page content.
 */
Selenium.prototype.assertTextPresent = function(expectedText) {
    var allText = this.page().bodyText();

    if(allText == "") {
        Assert.fail("Page text not found");
    } else if(allText.indexOf(expectedText) == -1) {
        Assert.fail("'" + expectedText + "' not found in page text.");
    }
};


こいつが何してるかっていうと、

function(expectedText)

は、コマンドの引数を expectedText という名前で受け取るという意味。


html のコマンドの

<tr>
    <td>assertTextPresent</td>
    <td>hoge</td>
    <td></td>
</tr>

hoge」という文字列が expectedText に入る。


んで、prototype 君には、page() というメソッドがあって、
これは現在表示中のページの情報が返ってくる(これを CurrentPage オブジェクトと呼ぼう)。


CurrentPage オブジェクト君は、表示されてる html の body 部(多分)を返す
bodyText メソッドを持ってるので、それを呼ぶ事で、
allText 変数にページの text が入る

    if(allText == "") {
        Assert.fail("Page text not found");
    } else if(allText.indexOf(expectedText) == -1) {
        Assert.fail("'" + expectedText + "' not found in page text.");
    }


Assert.fail は TestRunner で実行中のスクリプトを止めて、
ブラウザ上に赤く表示させるための処理。

else if(allText.indexOf(expectedText) == -1) は、
indexOf メソッドで、
ページの文字列中に expectedText (hoge) が現れる位置を取得。
文字列中に hoge が存在しなかったら、
indexOf メソッドは -1 を返す。


これが true になるってことは、
ページの中に hoge がないってことだから、
次の Assert.fail が呼ばれて、
TestRunner が赤くなるってすんぽー。


翻って、
俺が作った拡張コマンドは

/* 
 * assertTextPresentCount 
 */ 
Selenium.prototype.assertTextPresentCount = function(expectedTextReg, count) { 
    var allText = this.page().bodyText(); 
     
    if (allText.match(expectedTextReg, "g").length != count) { 
        Assert.fail("Page text not adapt"); 
    } 
};

ほぼ assertTextPresent コマンドをパクッってるんだけど
まず、
function(expectedTextReg, count)
で引数を2個取るように定義。


html の書き方としては、

<tr>
    <td>assertTextPresentCount</td>
    <td>hoge</td>
    <td>2</td>
</tr>

と書く。


これにより、
expectedTextReg に 「hoge」が入り、
count に 「2」が入る。


ページのテキスト受け取る部分は、assertTextPresent と一緒。



if (allText.match(expectedTextReg, "g").length != count)
のところで、
文字列の match メソッドを呼ぶ。
allText に expectedTextReg であらわす正規表現に適合する文字列の配列を
"g" オプションを指定することで、
適合する文字列全てを配列として返す。
そしてその配列の個数が count と合わなかったらエラー。


んで、このコマンド使いたいときは、
このソースを、user-extension.js に書いて、
サーバーで動かすなら、TestRunner.html から見える場所に user-extension.js 置くか、
Selenium Recorder(IDE も) から動かすなら、
File - Opetion の user-extension.js の設定を、
ローカルフォルダの user-extension.js の場所に設定すれば良い。


ま、こんな感じ。
酔っ払ってるわりにはしっかり書けたよね?!wwww


後日、Selenium 公式の拡張の仕方んところの日本語訳でもしてみんよー。