この記事はSelenium/Appium Advent Calendar 2016の7日目の記事です。
6日目は「Seleniumでブラウザーと一緒に用意しなきゃいけないアレの更新がめんどいのをどうにかしよう」でした。
#今更?
世間ではすでにselenium3に移行されていると思いますが、seleniumIDEネタです。
ロケーション指定の抽象化
テストケースからロケーションの具体的な記述はできるだけ分離したいところなのは言うまでもありません。
webdriver版では PageObjectsパターンがありますが、SeleniumIDEではどうすればよいでしょう。
そう、UI-Elementです。
なにそれ?美味しいの?? 状態だと思うのでこれについて書いてみます。
実例
日本Seleniumユーザーコミュニティの提供するサンプルページのサイトを利用させてもらい実例を見てみましょう。
まずは通常通り自動記録します。
記録ボタンを押してから、任意の値を入れて画面遷移してゆきます。
言うまでもなく、自動的に記録されていきます。
さて、ここでネタを仕込みます
公式では、オプションメニューの seleniumCore拡張スクリプトに設定とありますが
実行時に読み込まれていれば良いという点と、デバックのしやすさから
記録したテストケースの最初の行に下記を追加します。
<tr>
<td>getEval</td>
<td>
var map = new UIMap();
map.addPageset({
name:'reservePages'
,description:'contains elements set val page'
,pathRegexp:'.*'
});
map.addElement('reservePages',{
name:'reserve_var_year'
,description:'set year'
,locator:'id=reserve_year'
});
map.addElement('reservePages',{
name:'reserve_var_month'
,description:'set month'
,locator:'id=reserve_month'
});
map.addElement('reservePages',{
name:'reserve_var_day'
,description:'set day'
,locator:'id=reserve_day'
});
map.addElement('reservePages',{
name:'reserve_var_term'
,description:'set term'
,locator:'id=reserve_term'
});
map.addElement('reservePages',{
name:'reserve_var_headcount'
,description:'set headcount'
,locator:'id=reserve_headcount'
});
map.addElement('reservePages',{
name:'plana'
,description:'button plan'
,locator:'id=plan_a'
});
map.addElement('reservePages',{
name:'reserve_var_guestname'
,description:'set name'
,locator:'id=guestname'
});
map.addElement('reservePages',{
name:'goto_nextpage'
, description:'link to the next page'
,locator:'id=goto_next'
});
map.addElement('reservePages',{
name:'reserve_var_price'
,description:'set price'
,locator:'id=price'
});
map.addElement('reservePages',{
name:'reserve_go_commit'
,description:'button commit'
,locator:'id=commit'
});
map.addElement('reservePages',{
name:'result'
,description:'result value'
,locator:'css=h1'
});
map.addElement('reservePages',{
name:'reserve_go_checkInfo'
,description:'button goto checkinfo page'
,locator:'id=returnto_checkInfo'
});
map.addElement('reservePages',{
name:'reserve_go_index'
,description:'button goto index page'
,locator:'id=returnto_index'
});
var manager = new RollupManager();
manager.addRollupRule({
name: 'go_index',
description: 'goto index page',
args: [{
name: 'term',
description: 'the reserve term'
}],
commandMatchers: [{
command: 'clickAndWait',
target: 'ui=reservePages::reserve_go_checkInfo\\(.+'
},{
command: 'clickAndWait',
target: 'ui=reservePages::reserve_go_index\\(.+'
}],
getExpandedCommands: function (args) {
var commands = [];
var uiSpecifier = new UISpecifier(
'reservePages', 'reserve_var', {
term: args.term
});
commands.push({
command: 'clickAndWait',
target: 'ui=reservePages::reserve_go_checkInfo()'
});
commands.push({
command: 'clickAndWait',
target: 'ui=reservePages::reserve_go_index()'
});
return commands;
}
});
manager.addRollupRule({
name: 'set_year',
description: 'set var page',
args: [{
name: 'year',
description: 'the reserve year'
}],
commandMatchers: [{
command: 'type'
,target: 'ui=reservePages::reserve_var_year\\(.+'
,updateArgs:function(command, args){
args.year = command.value;
return args;
}
}],
getExpandedCommands: function (args) {
var commands = [];
var uiSpecifier = new UISpecifier(
'reservePages'
,'reserve_var'
,{ term: args.term });
commands.push({
command: 'type'
,target: 'ui=reservePages::reserve_var_year()'
,value: args.year
});
return commands;
}
});
</td>
<td></td>
</tr>
一度実行してみます。もちろん、オールグリーンで実行されます。
さて、このテストケースにもう一つテストケースを追加してみましょう。
操作は、最終行にカーソルを置いた状態で、記録ボタンを押すだけですね。
何ということでしょう!(古)今度の自動記録されたコマンドのロケーション指定がUIマップによる指定になっています。
実行も問題なしです。
このようにテストケースからロケーション指定の分離が簡単にできるわけです。
設計の段階でIDが確定していれば、それを元にUIマップによるエレメント指定を先行して作成しておき、画面が出来上がってから自動記録によるシナリオ作成が可能ということですね。
#One more Thing.
IDEの画面を良く見るといつもと異なる点に気づかれたでしょうか?。それは・・・
わかりましたか?
SeleniumIDEを利用している人で、このボタンを押したことがある人がどのくらいるの疑問ですが
これが、アクティブになっています!
これを押すとどうなるのか?
せっかくなので押してみましょう。
すると・・・・
16行目を書き換える替えるけどいいか?的な確認
さらに26行目と27行目を書き換えるけどいいか?的な確認
結果これまたほとんどの人が使ったことがないだろうと思われる、rollupコマンドに書き換わっています。
この機能、最初の置換は1コマンドを対象としているのであまり意味がありませんが2つ目の置換は2つのコマンドが1コマンドになっているのがわかると思います。
つまり、操作を束ねてブラックボックス化してしまえるわけです。
固定的操作をまとめてわかりやすい名前をつけておくことでテストケースの見通しを良くすることができます。
また、実行例で分かるとおり、熟練者がUI-elementsと合わせて作成しておけば、手動で記録したテストケースを自動的に変換するできるので作成のハードルも低くなりますね。
下記は、スクリーンショットを隠蔽してみたケースです。
<tr>
<td>getEval</td>
<td>
var map = new UIMap();
map.addPageset({
name: 'allpage',
description: 'All pages',
pathRegexp: '(/.+)?'
});
var manager = new RollupManager();
manager.addRollupRule({
name: 'do_PageScreenshot',
description: 'take page screenshot.',
args: [{
name: 'switch',
description: 'debugswitch',
exampleValues: [ 'on', 'off']
}, {
name: 'url',
description: 'terget Page url'
}]
,commandMatchers: {
command:'captureEntirePageScreenshot',
target: 'ss\.png$',
}
,getExpandedCommands: function (args) {
var timestmp = new Date().getTime();
var commands=[];
var filename = 'c:' + timestmp + '.png';
if (args.switch) {
if (args.url) {
commands.push({
command: 'open'
,terget: args.url
,values: ''
});
}
commands.push({
command: 'captureEntirePageScreenshot',
terget: filename,
values: ''
})
};
return commands;
}
});
</td>
<td></td>
</tr>
<tr>
<td>rollup</td>
<td>do_PageScreenshot</td>
<td>url=,switch=ON</td>
</tr>
##最後に
と、今更ながら(実は下書き状態で3年ほど眠っていました)に書きましたが、こちらの記事にある通り
1年先に SeleniumIDEが生き残っているかは今の所わかりません。
#お約束
webdriver版に移行しましょうね、というお話です。
個人的には SeleniumIDEが必要なシーンもあると思っていますので是非 WebExtensionsベースに移行していただきたいのですが・・・