UIマッピングを使って、Selenium IDEのテストスクリプトを効率よくメンテナンスする
ブラウザ操作の記録と再生が手軽にできるFirefoxアドオン、Selenium IDE。
Selenium IDEを使う上で面倒なのが、画面HTMLが変更されたときのテストスクリプトの修正です。同じ画面に対するスクリプトがたくさんあると、画面HTMLが変わるたびに全てのスクリプトを修正しなければなりません。
今日は、この「画面HTMLが変わった時のスクリプト修正コスト」をSelenium IDE上で軽減するテクニックとして、「UIマッピング」をご紹介します。
※ 以下の説明は、Selenium IDEの基本的な利用方法を知っていることを前提としています。
UIマッピングとは
「UIマッピング」は、SeleniumのスクリプトをJavaなどのプログラミング言語で書く時によく使われるテクニックで、Seleniumの公式ドキュメントでも紹介されている由緒正しい方法です。
まず、「UIマッピング」を使っていないSelenium IDEのテストスクリプトは、図1のようになります。
「name=your-name」「css=input.button」のような、操作対象の画面要素を特定するための記述(「ロケーター」と呼びます※1 )が、何箇所にも分散しています。
「UIマッピング」とは、この分散したロケーターを、UIマップファイルという1つのファイルで集中管理する手法です。(図2)
ロケーターに「inputName」「buttonSearch」などのわかりやすい別名をつけ、テストスクリプト中ではロケーターでなくこの別名を使用します。
UIマッピングを行うと次のようなメリットがあります。
- 画面のHTMLが変わった場合に、UIマップファイル中のロケーターだけを書き換えればよいので、メンテナンスの手間が大きく軽減されます。
- わかりやすい別名を使用することでテストスクリプトが読みやすくなります。
ちなみに、UIマッピングをさらに洗練させたページオブジェクトデザインパターンという手法もあるのですが、Selenium IDEでは実現できないので割愛します。
Selenium IDEでUIマッピングを使う
では次に、このUIマッピングのテクニックをSelenium IDEで使う手順を説明します。
今回は、TRIDENTが提供するSelenium動作テスト用サンプルページを使って説明します。
Selenium IDEでスクリプトを記録
まずは通常通り、Selenium IDEを使ってブラウザ操作を記録します。今回は、サンプルページに情報を入力し送信する操作を記録しました。(図3)
サンプルページですので、「送信」ボタンを押しても実際の送信には失敗します。
記録できたら、Selenium IDEの画面のメニューの「ファイル > テストケースを保存」から、記録したスクリプトを適当な名前で保存しておきます。
UIマップファイルの作成
続いて、先ほど記録したスクリプト中に登場したロケーターを、UIマップファイルに定義します。
まず、適当なフォルダに、user-extensions.jsという名前の空ファイルを作成します。このファイルがUIマップファイルになります。
なお、このときファイルの文字コードは"UTF-8"にしておきます。
次に、作成したuser-extensions.js中に図4のようなロジックを記述します。
var map = new UIMap(); var addPage = function(name, description) { var d; if (!description) { d = name; // description must not be empty } else { d = description; } map.addPageset({ name: name, description: d, pathRegexp: '.*' // maybe this value is not used in Selenium IDE }); }; var addToMap = function(pageset, name, locator, description) { var d; if (!description) { d = name; // description must not be empty } else { d = description; } map.addElement(pageset, { name: name, locator: locator, description: d }); }; addPage('contact'); addToMap('contact', 'inputName', 'name=your-name'); addToMap('contact', 'inputEMail', 'name=your-email'); addToMap('contact', 'inputOrganization', 'name=your-organization'); addToMap('contact', 'inputSubject', 'name=your-subject'); addToMap('contact', 'inputMessage', 'name=your-message'); addToMap('contact', 'buttonSend', 'css=input.wpcf7-form-control.wpcf7-submit');
「addPage」がページ名を登録する関数、「addToMap」がページ上のロケーターと別名のマッピングを登録する関数です。
なお、user-extensions.jsに日本語が含まれる場合、さらに追加の手順が必要です。詳しくは本記事下部の「UIマップファイルに日本語が含まれる場合の設定」を参照してください。
UIマップファイルの読み込み
続いて、Selenium IDEの画面のメニューの「オプション > 設定」を選び、「一般」タブの「Selenium Core拡張スクリプト(user-extensions.js)のパス」に、user-extensions.jsのファイルパスをフルパスで指定します。(図5)
設定が終わったら、一度Selenium IDEを再起動します。
再起動時にエラーメッセージが出る場合は、作成したuser-extensions.jsの記述に誤りがあるはずなので、エラーメッセージに従って修正してください。
スクリプト中のロケーターの書き換え
再起動したら、Selenium IDEの画面のメニューの「ファイル > テストケースを開く」から、先ほど保存したスクリプトを読み込み直します。
この状態で、コマンドの「対象」を変更しようとすると、UIマップファイルで定義した別名が候補に表示されるので、1つずつ変更していきます。(図6)
変更が終わったら、書き換えたスクリプトを実行してみましょう。うまくテストが実行できれば成功です。
UIマップファイルに日本語が含まれる場合の設定
user-extensions.jsに日本語が含まれている場合、Selenium IDE Version 2.2.0の時点では、文字化けが発生してうまく動作しません。(次期バージョンでこの問題は解消されると思われます※2 )
現時点でこの問題を回避するには、ちょっと面倒ですが次のようにします。
まず、user-extensions.jsのファイル名をui-map.jsに変更します。続いて、図8のような、日本語を一切含まないファイルuser-extensions.jsを作成します。
var yourScriptPath = '<your-uimap-file-path>/ui-map.js' var userExtensionLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); userExtensionLoader.loadSubScript('file://' + yourScriptPath, this, 'UTF-8');
1行目の「<your-uimap-file-path>/ui-map.js」の部分には、ui-map.jsのフルパスを記述します。※3
このuser-extensions.jsをSelenium IDEに読み込むようにすれば、user-extensions.jsがui-map.jsをUTF-8でロードするので、日本語が文字化けすることはありません。
もっと活用してみる
UIマップ自体を日本語で定義する
せっかく日本語が利用できるなら、UIマップ自体も日本語で定義すればスクリプトが読みやすくなります。
addPage('問い合わせ'); addToMap('問い合わせ', '名前', 'name=your-name'); addToMap('問い合わせ', 'E-mail', 'name=your-email'); addToMap('問い合わせ', '所属', 'name=your-organization'); addToMap('問い合わせ', '件名', 'name=your-subject'); addToMap('問い合わせ', '問い合わせ内容', 'name=your-message'); addToMap('問い合わせ', '送信ボタン', 'css=input.wpcf7-form-control.wpcf7-submit');
ロケーターの内容の確認
「対象」要素がUIマップを使っている場合に、Selenium IDEの画面下部の「UI-Element」タブでロケーターの値を確認することができます。(図10)
UIマップに、要素の詳細な説明を加える
UIマップを定義する際に要素の説明を加えておくと、Selenium IDEの画面下部の「UI-Element」タブで説明を確認することができます。
addPage('問い合わせ', 'TRIDENT社ウェブサイトの問い合わせ画面'); addToMap('問い合わせ', '名前', 'name=your-name', '名前を入力するエディット'); addToMap('問い合わせ', 'E-mail', 'name=your-email', 'E-mailを入力するエディット');
UIマップファイルをSelenium RCに流用する
作成したUIマップファイルは、Selenium RC※4のコマンドを使ったユニットテスト中でも利用することができます(残念ながらWebDriver形式のコマンドからは利用できません)。
利用するには、Selenium Serverを起動する際に、-userExtensionsオプションでuser-extensions.jsを指定しておきます。
java -jar selenium-server-standalone-<your-version>.jar -userExtensions <your-extension-full-path>/user-extensions.js
コマンド中の<your-version>と<your-extension-full-path>には、環境に応じた適当な値を指定してください。また、-userExtensionsオプションで指定するファイルの名前は必ずuser-extensions.jsという名前にする必要があります。
これで、Selenium RCのユニットテスト中でUIマップを参照することが可能になります。(図14)
import org.junit.After; import org.junit.Before; import org.junit.Test; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; public class UIMapTest { private Selenium selenium; @Before public void setUp() throws Exception { selenium = new DefaultSelenium( "localhost", 4444, "firefox", "http://www-demo.trident-qa.com/"); selenium.start(); } @Test public void test() throws Exception { selenium.open("/contact/"); selenium.type("ui=問い合わせ::名前()", "テスト"); selenium.type("ui=問い合わせ::E-mail()", "test@trident-qa.com"); selenium.type("ui=問い合わせ::所属()", "なし"); selenium.type("ui=問い合わせ::件名()", "問い合わせ"); selenium.type("ui=問い合わせ::問い合わせ内容()", "テスト送信"); // Selenium Server 2.33.0とFirefox22の組み合わせで // 「Value does implement interface Event」エラーが発生するので、 // 現状コメントアウト // selenium.click("ui=問い合わせ::送信ボタン()"); } @After public void tearDown() throws Exception { selenium.stop(); } }
問題点
最後に、使っていて不便だなあと思う点を挙げておきます。何か良い解決策を知っている方がいらっしゃいましたら、コメント欄などで教えていただけると大変嬉しいです。
- UIマップファイルの変更をSelenium IDEに反映させるためには、Selenium IDEを毎回再起動する必要があります。さらに、Firefox自体の再起動が必要な場合も多々あります(こちらは、日本語の文字化け回避のために、user-extensions.jsからさらにui-map.jsを読み込んだ場合に起きるようです)。
- Selenium IDE上で対象要素を選ぶ際に、UIマップに定義された全要素が候補として出てくるため、要素の数が増えると選ぶのが非常に大変になります。user-extensions.js中の「pathRegexp」を活用すればこの問題が解消されるのかと思ったのですが、残念ながらSelenium IDE上に反映されませんでした。
- 記録したスクリプトを毎回書き換えるのがけっこう大変です。記録した時点でUIマップを使ったスクリプトが生成されるといいのですが。
- 以前ご紹介した、「Selenium IDEを使って、ChromeやIE上でテストスクリプトを実行する方法」と同時に利用することはできません。
注釈・出典
- Locating Elements
- この不具合の修正パッチ をSeleniumプロジェクトに送ったら取り込んでもらえたので
- 本当はフルパスではなく、user-extensions.jsのあるフォルダからの相対パスで指定できた方が便利なのですが、良い方法が見つかりませんでした。
- Selenium RC とは?という方はこちらの記事をどうぞ