ブラウザ上で,マウスのドラッグ&ドロップも自動化できるスクリプト(UWSCでIEを自動操作するライブラリ 1.2)
※これより新しいバージョンがリリースされています。
http://d.hatena.ne.jp/language_and_engineering/20090918/p1
下記のライブラリを改良した。
UWSCでIEを自動操作し,回帰テスト/JavaScript実行/ファイル保存 などができるライブラリ
http://d.hatena.ne.jp/language_and_engineering/20090825/p1
変更点:
- マウスで,要素をドラッグ&ドロップする事が自動化可能に。
JavaScriptではマウス座標を操作することが不可能なので,今まで,複雑なUIをブラウザ上で回帰テストすることは不可能だった。
しかし今回のUWSC用ライブラリを使って,下のようなコードを書けば,3つのdiv要素を順番にドラッグドロップすることが可能。
hoge.uws
call .\IEManipulation.uws _ie = IE.new() IE.show( _ie ) IE.jump_with_js( _ie, "http://localhost/a.html" ) // ドラッグドロップ開始 // 要素のIDと,移動距離(x,y)と,所要時間を指定 IE.drag_drop( _ie, "fuga1", 100, 200, 3 ) IE.drag_drop( _ie, "fuga2", 200, 50, 3 ) IE.drag_drop( _ie, "fuga3", 40, 300, 3 )
これだけ。
UWSC.exeにこのファイルをドラッグドロップして動作が開始する。
テスト対象のWebページ:
a.html
<html> <head></head> <body> <br> <br> <br> <div id="fuga1" style="width:100px;height:100px;background-color:green">fuga1</div> <div id="fuga2" style="width:100px;height:100px;background-color:red">fuga2</div> <div id="fuga3" style="width:100px;height:100px;background-color:yellow">fuga3</div> <script language="JavaScript" src="prototype.js"></script> <script language="JavaScript" src="scriptaculous.js"></script> <script language="JavaScript" src="effects.js"></script> <script language="JavaScript" src="dragdrop.js"></script> <script language="JavaScript"> // ドラッグドロップ可能にする new Draggable("fuga1"); new Draggable("fuga2"); new Draggable("fuga3"); </script> </body> </html>
このためのライブラリ:
IEManipulation.uws
// // IEを自動操作するためのライブラリ ver 1.21 // class IE // -------------------- 制御 -------------------- // 新規IEオブジェクトを作成して返す function new() result = createOLEobj("InternetExplorer.Application") fend // 起動中のIEを見えるように procedure show( browser ) browser.visible = True // 位置調整 wid = hndtoid( browser.hwnd ) acw( wid, 0, 0 ) // 最大化 ctrlwin( wid, MAX ) // マウス相対座標をこのブラウザ基準に設定 MouseOrg( wid ) pause( browser ) fend // IEがビジー状態の間待ちます procedure wait( browser ) repeat sleep( 0.1 ) until ( ! browser.busy ) and ( browser.readystate = 4 ) pause( browser ) fend // URLにジャンプ procedure jump( browser, url ) browser.navigate( url ) wait( browser ) fend // ポーズ procedure pause( browser ) sleep( 0.2 ) fend // 要素が出現するまで待ちます procedure wait_for_element_present( browser, dom_id, timeout_sec ) interval_sec = 0.2 total_wait_sec = 0 loop_flag = True while loop_flag // 要素は現れたか ifb browser.document.getElementById( dom_id ) = Nothing then // 出現していないのでスリープ sleep( interval_sec ) total_wait_sec = total_wait_sec + interval_sec else // 出現したのでループ終了 loop_flag = false endif // タイムアウトか ifb total_wait_sec > timeout_sec then msgbox( "element '" + dom_id + "' did not appear." ) exitexit endif wend fend // -------------------- DOM操作 -------------------- // IDが渡された場合はDOM要素にして返します function to_elem( browser, locator ) ifb VarType( locator ) = 8 then // 変数の型が文字列の場合はDOM IDとみなす result = gid( browser, locator ) else // それ以外の場合はスルー result = locator endif // VarTypeのヘルプ:http://msdn.microsoft.com/ja-jp/library/cc392346.aspx fend // $ function gid( browser, dom_id ) result = browser.document.getElementById( dom_id ) fend // 入力 procedure type( browser, locator, str ) elem = to_elem( browser, locator ) elem.value = str pause( browser ) fend // クリック procedure click( browser, locator ) elem = to_elem( browser, locator ) elem.click pause( browser ) fend // クリックして待機 procedure click_and_wait( browser, locator ) click( browser, locator ) wait( browser ) fend // 文言ベースでセレクトボックスを選択 procedure select_by_label( browser, locator, label ) elem = to_elem( browser, locator ) for i = 0 to elem.options.length - 1 ifb elem.options[ i ].innerText = label then elem.options[ i ].selected = True endif next pause( browser ) fend // 値ベースでセレクトボックスを選択 procedure select_by_value( browser, locator, val ) elem = to_elem( browser, locator ) for i = 0 to elem.options.length - 1 ifb elem.options[ i ].Value = val then elem.options[ i ].selected = True endif next pause( browser ) fend // indexベースでセレクトボックスを選択 procedure select_by_index( browser, locator, index ) elem = to_elem( browser, locator ) elem.options[ index ].selected = True pause( browser ) fend // ファイルアップロード // DOM IDではなくnameで要素を指定するので注意 procedure file_upload( browser, post_name, file_path ) IESetData( browser, file_path, post_name ) pause( browser ) fend // -------------------- JavaScriptの制御 -------------------- // URLにジャンプし,WebページにJSコードを注入する procedure jump_with_js( browser, url ) IE.jump( browser, url ) IE.create_js_proxy( browser ) fend // Webページ中にJS経由用のオブジェクトを生成して返す procedure create_js_proxy( browser ) doc = browser.document TextBlock js_proxy_code // UWSCからコード注入するためのオブジェクト document._uwsc_proxy = { global : this, _window : window, eval_code : function( str ){ try{ return eval( str ); }catch( e ){ // エラーメッセージを表示 alert( e.number + " : " + e.description ); return null; } } }; endTextBlock // 生成 elem_s = doc.createElement("script") elem_s.text = js_proxy_code; elem_s.type = "text/javascript"; // 注入 doc.getElementsByTagName("head").Item(0).appendChild( elem_s ); fend // 文字列をJSコードとしてブラウザ側で評価 procedure export_js( browser, str_jscode ) browser.document._uwsc_proxy.eval_code( str_jscode ) fend // 文字列をJSコードとして評価した結果をUWSC側へ読み込み function import_js( browser, str_jscode ) // いったん文字列をブラウザ側にexportし,その結果をUWSC側にimport result = browser.document._uwsc_proxy.eval_code( str_jscode ) fend // -------------------- ファイルダウンロード用 -------------------- // ダイアログが現れるまで待機 procedure wait_for_dialog( dialog_title, timeout_sec ) interval_sec = 0.2 total_wait_sec = 0 loop_flag = True while loop_flag // ダイアログは現れたか ifb getid( dialog_title, "#32770", -1 ) > -1 then loop_flag = false else sleep( interval_sec ) total_wait_sec = total_wait_sec + interval_sec endif // タイムアウトか ifb total_wait_sec > timeout_sec then msgbox( "dialog '" + dialog_title + "' did not appear." ) exitexit endif wend fend // ダイアログにキーを送信 procedure send_dialog( dialog_title, key_code ) // 出現を待つ wait_for_dialog( dialog_title, 10 ) sleep(1) // キー押下 id = getid( dialog_title, "#32770", -1 ) sckey( id, key_code ) fend // ファイルのダウンロードダイアログが出たときに,ダウンロード+保存を実行 procedure save_downloaded_file( browser ) sleep(2) IE.send_dialog( "ファイルのダウンロード", vk_s ) IE.send_dialog( "名前を付けて保存", vk_return ) fend // -------------------- テスト実行用 -------------------- // 要素の値を検証 procedure assert_value( browser, locator, val_expected ) elem = to_elem( browser, locator ) val_real = elem.value assert( val_expected, val_real ) fend // 要素内の文字列を検証 procedure assert_text( browser, locator, val_expected ) elem = to_elem( browser, locator ) val_real = elem.innerText assert( val_expected, val_real ) fend // 文字列同士を比較 procedure assert( val_expected, val_real ) ifb val_expected = val_real then // OK else msgbox( "actual value '" + val_real + "' did not match '" + val_expected + "'" ) // スクリプトを強制終了 exitexit endif fend // -------------------- マウス操作用 -------------------- // ブラウザのbody(表示領域)からwindowまでの余白長を計算 procedure get_client_margins( browser, var xy_margin[] ) // mousemove時の挙動を定義 IE.export_js( _ browser, _ "document.onmousemove = function(){ " _ + "document._uwsc_proxy._mouse_x = event.x;" _ + "document._uwsc_proxy._mouse_y = event.y;" _ + "};" _ ) // 可視領域中だけの座標を見ればよいので,document.scrollTopを足さない // mousemoveイベントを発生させて,body内の座標を取得 LockHard(True) mouse_screen_x = 500 mouse_screen_y = 500 mmv( mouse_screen_x, mouse_screen_y ) mouse_client_x = IE.import_js( browser, "document._uwsc_proxy._mouse_x" ) mouse_client_y = IE.import_js( browser, "document._uwsc_proxy._mouse_y" ) LockHard(False) // 差分を取得 client_margin_x = mouse_screen_x - mouse_client_x client_margin_y = mouse_screen_y - mouse_client_y // 返却 xy_margin["x"] = client_margin_x xy_margin["y"] = client_margin_y fend // DOM要素のスクリーン上での座標を取得 procedure get_element_screen_position( browser, locator, var xy_elem_screen[] ) // 余白長を取得 HashTbl xy_margin IE.get_client_margins( browser, xy_margin ) client_margin_x = xy_margin["x"] client_margin_y = xy_margin["y"] // 要素のスクリーン上位置を計算 elem = IE.to_elem( browser, locator ) elem_client_y = elem.offsetTop - IE.import_js( browser, "document.body.scrollTop" ) elem_client_x = elem.offsetLeft - IE.import_js( browser, "document.body.scrollLeft" ) elem_screen_y = elem_client_y + client_margin_y elem_screen_x = elem_client_x + client_margin_x // 返却 xy_elem_screen["x"] = elem_screen_x xy_elem_screen["y"] = elem_screen_y fend // 要素の上にマウスを移動 procedure mouse_over_on_elem( browser, locator, var xy_elem_center_screen[] ) elem = IE.to_elem( browser, locator ) // 要素のスクリーン上の位置を取得 HashTbl xy_elem_screen IE.get_element_screen_position( browser, elem, xy_elem_screen ) elem_screen_x = xy_elem_screen["x"] elem_screen_y = xy_elem_screen["y"] //msgbox( elem_screen_x + " " + elem_screen_y ) // 要素の中心へマウス移動 elem_center_x = elem_screen_x + ( Val( ChgMoj( elem.offsetWidth, "px", "" ) ) / 2 ) elem_center_y = elem_screen_y + ( Val( ChgMoj( elem.offsetHeight, "px", "" ) ) / 2 ) //msgbox( elem_center_x + " " + elem_center_y ) mmv( elem_center_x, elem_center_y ) sleep(1) // 開始点を返す xy_elem_center_screen["x"] = elem_center_x xy_elem_center_screen["y"] = elem_center_y fend // 指定要素のドラッグを開始 procedure start_drag( browser, locator, var xy_start_screen[] ) elem = IE.to_elem( browser, locator ) // 要素の中心へマウス移動 HashTbl xy_elem_center_screen IE.mouse_over_on_elem( browser, elem, xy_elem_center_screen ) drag_start_x = xy_elem_center_screen["x"] drag_start_y = xy_elem_center_screen["y"] // 要素をクリック btn( LEFT, DOWN, drag_start_x, drag_start_y ) sleep(1) // 開始点を返す xy_start_screen["x"] = drag_start_x xy_start_screen["y"] = drag_start_y fend // 指定要素をドラッグドロップ // 要素と,移動距離と,所要時間を指定 procedure drag_drop( browser, locator, move_by_x, move_by_y, total_sec ) elem = IE.to_elem( browser, locator ) // ドラッグ開始 HashTbl xy_start_screen IE.start_drag( browser, elem, xy_start_screen ) start_screen_x = xy_start_screen["x"] start_screen_y = xy_start_screen["y"] // 移動 LockHard(True) interval_sec = 0.2 // ループ一回にかかる秒数 move_step_num = total_sec / interval_sec // 移動に要するステップ数 step_x = move_by_x / move_step_num // 1ステップの移動距離 step_y = move_by_y / move_step_num moved_x = 0 // 累積移動距離 moved_y = 0 i = 0 while i < move_step_num moved_x = step_x * ( i + 1 ) moved_y = step_y * ( i + 1 ) mmv( start_screen_x + moved_x, start_screen_y + moved_y ) sleep( interval_sec ) i = i + 1 wend // 左ボタンを上げる btn( LEFT, UP, start_screen_x + move_by_x, start_screen_y + move_by_y ) LockHard(False) sleep(1) fend endclass
mouse_over_on_elemという関数で,要素の上にマウスを移動させることができる。
実装上のツボ:
- HashTblを構造体のつもりで扱い,xy座標を持ちまわらせている。
- ブラウザの「windowの端」から「bodyの端」までの長さを求めるところが面倒。
- IEではwidthもstyle.widthも取得できなかったりする。そこでかわりにoffsetWidth / offsetHeightを使っている。
onmousemoveイベントを使っているので,一部のページでは動作しない。
補足
Webアプリがリッチになるにつれ,Selenium系の限界を感じるようになる。
複雑なUIも自動で回帰テストができればかなり楽。
UWSCなら可能。
※なお,最初はWSHでオートメーションを組もうとがんばったが,不安定でつまずいた。
関連する記事:
UWSCのマクロで,IEを起動して自動操作するサンプルコード - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20140204/controlIeBrowserbyUWSC
JScript / VBScript (WSH)で,IEを自動操作しよう - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20090713/p1
IE AutoTester で,UIの回帰テストを完全自動化 - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20090922/p1