スポンサーリンク

Selenium 中級者になろう (変数+XPath+JavaScriptを,テストケース中で利用する方法)

回帰テストツール「Selenium」の中級 Tips。


初級の使い方については

今から3分で selenium の使い方を身に付ける (回帰テスト自動化)
http://language-and-engineering.hatenablog.jp/entry/20081016/1224080409


selenium 主なコマンド一覧
http://language-and-engineering.hatenablog.jp/entry/20081016/1224123118

で入門のこと。



Seleniumのより便利な使い方として,下記で

  • (1)変数の使い方
  • (2)XPathの使い方
  • (3)テストケース中へのjavascriptの埋め込み
  • (4)Ajaxアプリのテスト方法

を学ぶ。

まず,まとめを掲載。そのあとで,実際のテストでどう役立つのか詳しく解説する。


まとめ

要素の指定方法まとめ

ElementLocator(要素を指定するための文字列)にはDOM ID,xpath, document.〜,javascript{"DOM"+"ID";}, ${varname}が使える。

  • verifyElementPresent fuga
  • verifyElementPresent //input[@id="fuga"]
  • verifyElementPresent document.getElementById("fuga")
  • verifyElementPresent document.getElementsByTagName("input")[0]
  • verifyElementPresent javascript{ "fu" + "ga"; }
  • store fuga elem_id
    verifyElementPresent ${elem_id}
  • store fuga elem_id
    verifyElementPresent javascript{ storedVars["elem_id"]; }
変数のまとめ

格納

  • store 値 変数名
  • storeValue 要素 変数名
  • getEval storedVars["変数名"] = 値;

参照

  • ${変数名}
  • javascript{ storedVars["変数名"] }
JSのまとめ

埋め込み

  • javascript{ コード }
  • getEval コード
  • verifyEval コード 値

JSコード中で利用できる特殊なオブジェクト

  • this : selenium内の専用環境を指す
  • this.page().getDocument() : テスト中のページのdocument
  • this.page().getCurrentWindow() : テスト中のページのwindow
  • storedVars : 変数格納用のハッシュ
  • selenium : seleniumコアのいろいろ操作とかできるオブジェクト


以下解説。


(1)Seleniumで変数を使う


下記ではこんなHTMLを想定。

ユーザ名:<input type="text" id="input1">

あいさつ:<input type="text" id="input2">
変数をそのまま使う

store系のコマンドで保存。${}で変数呼び出し。

「input1」というIDのDOM要素に,「user1」と入力したい。

# 入力内容の文字列として,str_usernameという変数に格納したuser1という値を利用

store  user1   str_username
type   input1  ${str_username}


# 入力対象の要素IDとして,input_usernameという変数に格納したinput1という値を利用

store  input1            input_username
type   ${input_username} user1

具体的なテストデータや要素IDは,テストケースの先頭で変数として宣言しておけば,いざという時に変更は一箇所ですむ。
つまり,テストケースの保守が容易になる。


なお,${varname}の部分はjavascript{ storedVars["varname"]; }としても同じ。

storedVarsは,変数を格納するための連想配列。

変数をJavaScriptで加工して使う

「input1」という要素のvalueに,名前が入っているとする。

これを「Hello, 〜!」のように加工してから,input2のvalueに入力したい。

storeValue  input1  str_username
type        input2  javascript{ "Hello, "+storedVars["str_username"]+"!"; }

JavaScriptから変数を参照すれば,保管した値を好きなように加工できる。



「今日の日付」とか「乱数」とかを扱う場合,テストケース中で固定値を書くことは不可能(値を予測することができない)。

だからこのように

  • Webページ上にその時点で表示されている値を見て,
  • それを変数に格納して,
  • 他の値と比較

という風にして,柔軟に対処できる。


(2)SeleniumでXPathを使う

シンプルなHTMLならば,DOM IDだけでテスト可能だ。

でも,動的に変化するようなWebページの場合,各要素にIDを振りづらい。それに

  • 特定のCSSクラスであるような n 番目のdiv要素
  • IDが「hoge_」で始まる全要素

みたいな指定はできない。


しかし,XPathなら可能。

jQueryのCSSセレクタのように,強力に要素選択できる。

→なので

  • Webページが複雑な構造でもテストが怖くない
  • テストのために全部IDを振りなおしてくれ,みたいな事を開発者・デザイナ・テスター間で調整して回らなくて済む。

というメリットがある。ただ,記法を覚える必要があるが。

XPathの基礎知識

独習に役立つようなWebページは非常に少ない。以下参考URL

XMLパス言語 (XPath) Version 1.0
http://www.doraneko.org/xml/xpath10/1...
仕様書。これだけ読んで理解するのは不可能


JavaScript-XPath をリリースしました!さあ、あなたも XPath を使おう!(解説付き)
http://d.hatena.ne.jp/amachang/200711...
チートシート有


XPath入門、実用例
http://d.hatena.ne.jp/javascripter/20...

  • javascripter氏によるサンプルの列挙


XPathの学習方法としては,FireBugのコンソールでリアルタイムで確かめてみる,というのが最も良い方法だろう。


FireBugを開き,コンソールの一番下の「>>>」と書いてある行に

$x("//*")

と入力し,エンターキーを押下。すると,表示中のページの全要素が列挙される。

今度は

$x("//td/a[@href]")

とすれば,td要素の子要素であるようなaリンク要素がすべて列挙される。



$x( XPath記法 ) のようにして,

  • その記法が実際にどの要素を現すか
  • 正しい記法か

確かめられるというわけだ。


省略記法を覚えれば,要素指定は短くなる。

例えば,

  • //table[@id="hoge"]/tbody/tr[1] と書く代わりに,
  • //table[@id="hoge"]//tr[1] と書いてよい。

※//は「それ自身か,または子孫全体の中から選ぶ」という意味。


注意点として,ノードセット(ノードの配列)の指定は間違いやすい。

「現在のページ内に存在する2つめのリンク」を指定したい場合,

$x("//a[@href][2]")

ではだめ。「何かの親要素の2番目の子要素として存在しているa要素」がすべて列挙されてしまう。

かわりに

$x("descendant::a[@href][2]")

とすればOK。

XPathにおける//*とdescendant::*の違い
http://d.hatena.ne.jp/os0x/20080711/1...

Seleniumテストケース内で,Xpathで要素を指定する
  • //
  • xpath=

のいずれかで始めると,xpath記法として解釈される。


Seleniumでは,要素の存在判定のときにXPathが便利。

判定したい要素に特定のclassを持たせておいて,そういうクラスの要素が存在するかどうか確かめればよい。

#hogeというclassのspan要素が存在することを確認

assertElementPresent //span[@class="hoge"]


#hogeというclassのspan要素が【2個】存在することを確認

verifyElementPresent xpath=descendant::span[@class="hoge"][1]
verifyElementPresent xpath=descendant::span[@class="hoge"][2]

つまり,XPathを利用して,getElementsByClassNameしている。

メッセージや文言の表示確認などで便利。


(3)要素や値の指定以外にも,JavaScriptを利用してみよう

(1)で既に見たとおり,javascript{〜}によって要素IDとか値とかを指定できる。

それ以外の方法でも,JSコードを埋め込み可能。

Seleniumのテストケース中で,任意のJavaScriptコードを実行したい
# hogeとアラート表示

getEval alert("hoge");


# ページ内任意要素のinnerHTMLをアラート表示

getEval alert( this.page().getDocument().getElementById("div_content").innerHTML );

特に後者は,テストの途中でページ内の状況を確かめることができて便利。

Selenium内JavaScript実行用コマンド「getEval」
http://colo-ri.jp/develop/2008/04/sel...


なお,

  • this.page().getDocument()は,テスト対象のページのdocumentを指す。
  • 単なる「document」は,getEvalのようなJSコード中では,Seleniumのテストツールのある画面が取得されてしまう。

windowについても同じで,

  • this.page().getCurrentWindow()は,テスト対象のページのwindowを指す。
  • 単なる「window」は,Seleniumのテストツールのある画面を指してしまう。

よって,もしテスト対象のページ内で$()という関数が定義されていれば,this.page().getCurrentWindow().$()によって呼び出せる。


また,user-extensions.js中に関数を定義しておけば,テストケース内でその関数を呼び出すこともできる。

Seleniumに外部JavaScriptファイル(.js)を読み込んで関数を実行する
http://colo-ri.jp/develop/2008/04/sel...

しかし,これはあまり得策ではないだろう。テストツールに手を加えたら,テストのテストをしなきゃならないので・・・


値の高度な比較

verifyEvalとかassertEvalを使えば,JavaScriptを介して複雑なAssertionが実現できる。

たとえば下記のようなHTMLを想定:

きょうの日付は<span id="hoge">2009年1月1日</span>

<span id="fuga">2009/1/1</span>の行動予定:〜〜

フォーマットは異なるけども,同じ値が入っていてほしい。
しかも,テストを実行する日付しだいで,実際に画面上に表示される日付も変わってしまう。

というような場合。

# まずspanの中身を変数に格納して

storeText   hoge    str_date1
storeText   fuga    str_date2


# 互いに比較

verifyEval  storedVars["str_date1"].match(/(.*)年(.*)月(.*)日/);RegExp.$1+"/"+RegExp.$2+"/"+RegExp.$3;  ${str_date2}

hogeのほうを加工して,fugaに等しくなるかどうかをチェックしている。

なお,コードブロック内で最後に評価した値(ここではRegExp.$1+"/"+RegExp.$2+"/"+RegExp.$3)が,比較のために利用される。(Rubyっぽい)


もうちょっと簡単なサンプル:

# ページのタイトルを変数に保存してから
store      javascript{this.page().getDocument().title}  fuga

# 変数とページのタイトルを比較。(当然ながら互いに等しい)
verifyEval this.page().getDocument().title              ${fuga}

書き方にはいろいろ幅がある。

ある変数の中身について,文字列の長さを確かめたい場合は

# 数値と比較
verifyEval  storedVars["hoge"].length     5

# 真偽値と比較
verifyEval  storedVars["hoge"].length==5  true

のように,2通りの書き方ができる。


このようにSeleniumの世界にJavaScriptを持ち込むことができれば,もうあとはやりたい放題にテストできるはず。

(4)Ajaxアプリのテスト

div_ajaxという要素内に,AjaxでHTMLを読み込むとしよう。

その読み込みが終了するまで待つには,waitFor〜〜コマンドを使う。

# 要素の中に「読み込み完了」という文字列が含まれる状況になるのを待つ。10秒でタイムアウト。

waitForCondition   var val=selenium.getText("div_ajax");val.match(/読み込み完了/)!=null; 10000


特定の要素が読み込みによって出現するのを待っても良い。

# 読み込みによってh3要素が現れるのを待つ

waitForElementPresent  xpath=//h3[@class="content_loaded"]  10000

あるいは,ローディング中のクルクル回る画像が消えたのを,waitForElementNotPresent で検出してもOK。

SeleniumでAjaxアプリケーションをテストする
http://www.infoq.com/jp/articles/test...
何かをチェックためのverifyXxxxやassertXxxxがあるなら、非同期効果をテストするためのwaitForXxxxが必ず存在します。

以上のことを知っておけば,やりたいことはほぼSeleniumで実現できるだろう。


関連する記事:

"Excelenium"(エクセレニウム)で,快適な自動回帰テストを  (Seleniumのテストスクリプトとテスト仕様書を自動生成)
http://language-and-engineering.hatenablog.jp/entry/20090524/p1


Androidアプリの自動テストツールで最も有望か - 「NativeDriver」,Google製「WebDriver」の拡張 (公式のAndroid版Selenium)
http://language-and-engineering.hatenablog.jp/entry/20110930/p1


IE AutoTester で,UIの回帰テストを完全自動化
http://language-and-engineering.hatenablog.jp/entry/20090922/p1