スポンサーリンク

プロジェクト専用のDSLで,効率的にIEを自動操作する(WSH/JScript)



以下のエントリでは,JScriptでブラウザのオートメーションを行なうための基礎を述べた。

JScript / VBScript (WSH)で,IEを自動操作しよう
http://d.hatena.ne.jp/language_and_engineering/20090713/p1


その続編として,プロジェクト内でオートメーションスクリプトを使うための実用的なファイル構成を述べる。

プロジェクトに特化した便利メソッド(DSL)を作って,楽にブラウザを動かそう」というコンセプト。

概要

ファイル構成:

  1. 実行.bat
    • ie.wsfを起動するためのバッチ。
  2. ie.wsf
    • プロジェクトの機能を実行するファイル。
    • eg. 具体的なユーザ名を利用したログインなど。
  3. lib_site.js
    • プロジェクトの機能を定義するファイル。
    • eg. ログインなど。
  4. lib_ie.js
    • ブラウザの機能を定義するファイル。
    • eg. DOM操作など。

上の階層が下の階層を呼び出している。



4には,IEを自動操作するための便利メソッドをたくさん詰め込んでおく。
普段はあまり編集しない。


3には,対象プロジェクトのWebアプリケーションを操作するための便利メソッドを書く。
いわば,ユースケースの雛形。
プロジェクトの機能を利用するためのDSLとも言える。


2は,3と4を呼び出している。
3でメソッドを定義しておけば,2では,多数の具体的なユースケースを簡潔に記述できる。

※ユースケースを直接wsf上に記述するのではなく,外部jsファイルに切り分けてもOK。wsfはそれを呼び出せばよい。
特に,コード量が増えてきたらそうする必要がある。


1は,2をキックするだけ。wsfの実行方法がわからない,という人もいるから。




具体的なファイル内容のサンプルは下記の通り。



(1)キック用のバッチ


実行.bat

cscript ie.wsf

(2)ユースケース

ie.wsf

<job>
	<!-- IE自動操作のためのライブラリ -->
	<script language="JavaScript" src="lib_ie.js"></script>
	
	<!-- プロジェクトの機能を利用するためのライブラリ -->
	<script language="JavaScript" src="lib_site.js"></script>
	
	<!--  プロジェクトの機能の呼び出しを以下に記述します。 -->
	<script language="JavaScript">
	
	var ie = new IE();

	open_top_page();
	replace_body_html();
	
	// 検索実行
	search_by_keyword( "hoge" );
	search_by_keyword( "fuga" );
	search_by_keyword( "boo" );
	
	ie.quit();
	
	</script>
</job>

(3)ユースケース雛形

lib_site.js

// プロジェクトに特化したメソッドを記述します。


// トップページを開く
function open_top_page()
{
	ie.goto_url( "http://www.yahoo.co.jp" );
}


// 画面上の表示を加工する
function replace_body_html()
{
	ie.exec_js(function(){
		document.body.innerHTML = document.body.innerHTML.replace( /ー/g, "━━━(+A+)━━━" );
		focus();
	});
	
	ie.sleep( 1000 );
}


// キーワード検索
function search_by_keyword( kwd )
{
	open_top_page();
	
	ie.type( "srchtxt", kwd );
	ie.click_and_wait( "srchbtn" );
	
	ie.sleep( 1000 );
}

具体的にどういうキーワードで検索するのか,という情報は,(3)ではなく(2)に書く。


(4)ブラウザ操作用のライブラリ

lib_ie.js

// IE自動操作のためのクラス

var IE = function( obj ){
	this._build( obj );
};
IE.prototype = {
	// ブラウザオブジェクト
	_ie : null,
	
	
	// ---------- セットアップ系 ----------
	
	
	// 初回セットアップ
	_build : function( obj ){
		this._create_new_ie();
		
		var default_url = "about:blank";
		if( obj && obj[ "url" ] )
		{
			default_url = obj[ "url" ];
		}
		this.goto_url( default_url );
		
		this.set_visible( true );
	}
	,
	
	// 新規IEをセット
	_create_new_ie : function(){
		this._ie = WScript.CreateObject( "InternetExplorer.Application" );
	}
	,
	
	// 可視状態
	set_visible : function( bool ){
		this._ie.Visible = bool;
	}
	,
	
	// 終了
	quit : function(){
		this._ie.Quit();
		this._ie = null;
	}
	,
	

	// ---------- システム待機系 ----------

	
	// ビジー状態の間待つ
	wait_while_busy : function(){
		var timeout_ms      = 10000;
		var step_ms         = 100;
		var total_waited_ms = 0;
		
		while( this.is_busy() )
		{
			this.sleep( step_ms );
			
			// タイムアウトか?
			total_waited_ms += step_ms;
			if( total_waited_ms >= timeout_ms )
			{
				this.debug(
					"警告:タイムアウトのため,リロードします。("
					+ this.get_current_url()
					+ ")"
				);
				this.reload();
				
				break;
			}
		}
		
		this.sleep( 500 );
	}
	,
	
	// ビジー状態か判定
	is_busy : function(){
		return ( ( this._ie.Busy ) || ( this._ie.readystate != 4 ) );
	}
	,
	
	// 指定ミリ秒だけ待機
	sleep : function( ms ){
		WScript.Sleep( ms );
	}
	,


	// ---------- アドレスバー関連 ----------


	// ページを移動
	goto_url : function( url ){
		this._ie.Navigate( url );
		this.wait_while_busy();
	}
	,
	
	// ページをリロード
	reload : function(){
		this._ie.document.location.reload( true );
			// http://www.microsoft.com/japan/technet/scriptcenter/resources/qanda/sept05/hey0927.mspx
		this.wait_while_busy();
	}
	,
	
	// 現在表示中のページのURL
	get_current_url : function()
	{
		return this._ie.LocationURL;
			// http://blog.livedoor.jp/programlog/archives/298228.html
	}
	,
	
	// 受け取ったJavaScript関数オブジェクトをブラウザ上で実行します
	exec_js : function( func )
	{
		var str_address = "javascript:(" + func.toString() + ")();void(0);";
			//this.debug( str_address );
			// toSource()のかわりにtoString()を
			// http://blog.livedoor.jp/dankogai/archives/50957994.html
		
		this._ie.Navigate( str_address );
	}
	,


	// ---------- DOM操作 ----------

	
	// セーフな要素取得
	$ : function( dom_id ){
		
		// 10秒までは待ってあげる
		this.wait_for_element_present( dom_id, 10000 );
		
		return this.gid( dom_id );
	}
	,
	
	
	// IDで要素取得
	gid : function( dom_id ){
		return this._ie.document.getElementById( dom_id );
	}
	,
	
	
	// 存在判定
	is_element_present : function( dom_id ){
		return ( this.gid( dom_id ) != null );
	}
	,
	
	
	// 存在待ち
	wait_for_element_present : function( dom_id, ms_timeout )
	{
		var ms_spent = 0;
		while( true )
		{
			// 要素が現れたか?
			if( this.is_element_present( dom_id ) )
			{
				break;
			}
			else
			{
				ms_spent += 100;
				this.sleep( 100 );
			}
			
			// タイムアウトか?
			if( ms_timeout <= ms_spent )
			{
				this.debug( dom_id + "が存在しません。" );
				break;
			}
		}
		
		return;
	}
	,
	
	
	// 入力
	type : function( dom_id, value ){
		this.$( dom_id ).value = value;
	}
	,
	
	// クリック
	click : function( dom_id ){
		this.$( dom_id ).click();
	}
	,

	// クリックして待機
	click_and_wait : function( dom_id ){
		this.click( dom_id );
		this.wait_while_busy();
	}
	,


	// セレクトボックス(文言ベース)
	select_by_label : function( dom_id, target_label )
	{
		var opts = this.$( dom_id ).options;
		for( var i = 0; i < opts.length; i ++ )
		{
			if( "" + opts[i].innerText == "" + target_label )
			{
				opts[i].selected = true;
			}
		}
		this.$( dom_id ).fireEvent( "onchange" );
	}
	,


	// ---------- デバッグ用 ----------


	debug : function( str )
	{
		WScript.Echo( str );
	}

};

解説:

  • InternetExplorer.ApplicationというActiveXオブジェクトそのものを拡張することは不可能。だからかわりに,そのオブジェクトをフィールドに持つようなラッパークラスを定義して,やりたい放題にメソッドを追加する。つまりコンポジション
  • exec_jsは,クロージャを受け取ってブラウザ上でブックマークレットとして実行してくれる。例えば,wsf側では alert() という関数が定義されていなくても,wsf内のクロージャ内に alert() と書くことができる。クロージャはすばらしい。
  • ブラウザの自動操作につきものの問題は,タイムアウトだ。しかし,このコードなら,タイムアウト発生を検出した時点でページは自動的にリロードされ,オートメーションは中断しない。

補足

Selenium RCのテストスクリプトと似たイメージ。

Part4 テストを自動化する注目のツールSelenium:こまめなテストがバグ早期発見の決め手に
http://itpro.nikkeibp.co.jp/article/COLUMN/20071011/284284/?ST=develop&P=3

一番下の test会員登録() というメソッドを参考に

本稿は,Webアプリケーションへのテストデータ投入のような定型操作などに利用可。