LoginSignup
4
2

More than 5 years have passed since last update.

Selenium IDE で PageObjectsパターン

Posted at

この記事はSelenium/Appium Advent Calendar 2016の7日目の記事です。
6日目は「Seleniumでブラウザーと一緒に用意しなきゃいけないアレの更新がめんどいのをどうにかしよう」でした。

今更?

世間ではすでにselenium3に移行されていると思いますが、seleniumIDEネタです。

ロケーション指定の抽象化

テストケースからロケーションの具体的な記述はできるだけ分離したいところなのは言うまでもありません。
 webdriver版では PageObjectsパターンがありますが、SeleniumIDEではどうすればよいでしょう。
そう、UI-Elementです。

なにそれ?美味しいの?? 状態だと思うのでこれについて書いてみます。

実例

日本Seleniumユーザーコミュニティの提供するサンプルページのサイトを利用させてもらい実例を見てみましょう。

まずは通常通り自動記録します。
記録ボタンを押してから、任意の値を入れて画面遷移してゆきます。
言うまでもなく、自動的に記録されていきます。

スクリーンショット 2016-12-05 19.00.54.png

さて、ここでネタを仕込みます
公式では、オプションメニューの seleniumCore拡張スクリプトに設定とありますが

スクリーンショット 2016-12-05 19.02.38.png

実行時に読み込まれていれば良いという点と、デバックのしやすさから
記録したテストケースの最初の行に下記を追加します。

<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マップによる指定になっています。
実行も問題なしです。

スクリーンショット 2016-12-05 19.18.06.png

コンテキストメニューにも反映されています。
スクリーンショット 2016-12-05 19.07.30.png

このようにテストケースからロケーション指定の分離が簡単にできるわけです。
設計の段階でIDが確定していれば、それを元にUIマップによるエレメント指定を先行して作成しておき、画面が出来上がってから自動記録によるシナリオ作成が可能ということですね。

One more Thing.

IDEの画面を良く見るといつもと異なる点に気づかれたでしょうか?。それは・・・

スクリーンショット 2016-12-05 20.22.01.png

わかりましたか?

ちなみに、通常はこちら
スクリーンショット 2016-12-05 20.20.48.png

そう!これです、これ!!
スクリーンショット 2016-12-05 19.19.50.png

SeleniumIDEを利用している人で、このボタンを押したことがある人がどのくらいるの疑問ですが
これが、アクティブになっています!

これを押すとどうなるのか?
せっかくなので押してみましょう。
すると・・・・

16行目を書き換える替えるけどいいか?的な確認

スクリーンショット 2016-12-05 19.21.13.png

さらに26行目と27行目を書き換えるけどいいか?的な確認

スクリーンショット 2016-12-05 19.21.41.png

結果これまたほとんどの人が使ったことがないだろうと思われる、rollupコマンドに書き換わっています。

スクリーンショット 2016-12-05 19.21.53.png

この機能、最初の置換は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ベースに移行していただきたいのですが・・・

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2