WSH(JScript)用ブラウザ操作補助:WebBrowser.js

WSH(JScript)用ブラウザ操作補助です。
機能概要は、以下の通りです。

  • ブラウザ操作補助
  • 操作ログの出力/キャッシュ

スクライピング用に作成しました。エラー時のキャッシュ保存や、操作ログを出力しながらの動作確認を想定しています。また、自動化のためにalertのconsole化などを行います。

サンプル

browser.wsf<?xml version="1.0" encoding="UTF-16" standalone="yes" ?>
<package>
  <job>
    <?job error="false" debug="true" ?>
    <script language="JavaScript" src="../lib/json3.min.js"/>
    <script language="JavaScript" src="../common/Console.js"/>
    <script language="JavaScript" src="../common/WebBrowser.js"/>
    <script language="JavaScript">
//<![CDATA[
(function() {
  "use strict";

  function main() {

    var browser = new WebBrowser({
        debug: true,            // デバッグモード(ログ動的出力)
        //visible: false,       // IE非表示
        dummy: null
    });

    browser.Navigate('https://translate.google.co.jp/?hl=ja');
    var source = browser.querySelector('#source');
    source.innerText = 'test';
    WScript.Sleep(2*1000);      // 目視用(削除可能)

    var result = browser.querySelector('#gt-res-content').innerText;
    WScript.Sleep(2*1000);      // 目視用(削除可能)

    browser.Quit();
    WScript.Echo(result);
  }

  main();
})();
//]]>
    </script>
  </job>
</package>

実行結果例

> cscript browser.wsf
WebBrowser_clearCache, {"debug":true,"dummy":null,"visible":true,"top":-1,"left":-1,"width":-1,"height":-1,"confirm":true,"prompt":"\"\"","random.sleep.min":5000,"random.sleep.max":10000},
WebBrowser_Navigate, true, https://translate.google.co.jp/?hl=ja
WebBrowser_querySelectorAll, 1, https://translate.google.co.jp/?hl=ja
WebBrowser_querySelectorAll, 1, https://translate.google.co.jp/?hl=ja#en/ja/test
テスト

※補足:ErrorUtility.jsを使用すると、実行関数の引数まで表示されるようになる

参考リンク

WebBrowser.js

GitHubで公開しました。最新版は、k08045kk/WSHLibraryから取得してください。

WebBrowser.js/*!
 * WebBrowser.js v2
 *
 * Copyright (c) 2018 toshi
 * Released under the MIT license.
 * see https://opensource.org/licenses/MIT
 */

/**
 * WSH(JScript)用ブラウザ操作補助
 * Console.jsと組み合わせることで実行中ログ出力機能を追加する。
 * Console.Animation.jsと組み合わせることで待機待ちをアニメーション表示する。
 * ErrorUtility.jsと組み合わせることで呼び出し元関数の引数までトレースする。
 * @requires    module:ActiveXObject("InternetExplorer.Application")
 * @requires    module:JSON
 * @requires    module:WScript
 * @requires    ErrorUtility.js
 * @requires    Console.js
 * @requires    Console.Animation.js - v5+
 * @auther      toshi(https://www.bugbugnow.net/)
 * @license     MIT License
 * @version     2
 * @see         1.20180319 - add - 初版
 * @see         2.20190823 - update - Navigateを閲覧中の場合、再閲覧しないように仕様変更
 * @see         2.20190823 - update - doNavigateを追加
 * @see         2.20190823 - update - アニメーション関連をリファクタリング
 * @see         2.20190823 - update - コマンドライン引数を自動で確認しないように変更
 * @see         2.20190918 - fix - Navigateの戻り値がなくなっていた
 */
(function(root, factory) {
  if (!root.WebBrowser) {
    root.WebBrowser = factory(root.ErrorUtility, root.Console);
  }
})(this, function(ErrorUtility, Console) {
  "use strict";

  // -------------------- private --------------------

  var _this = void 0;

  /**
   * PrivateUnderscore.js
   * @version   4
   */
  {
    function _isString(obj) {
      return Object.prototype.toString.call(obj) === '[object String]';
    };
    function _isArray(obj) {
      return Object.prototype.toString.call(obj) === '[object Array]';
    };
    function _isElement(obj) {
      return !!(obj && obj.nodeType === 1);
    };
    function _getCallee() {
      var args = arguments;
      var func = args.callee;
      return func.caller;
    };
    function _extend(dst, src, undefinedOnly) {
      if (dst != null && src != null) {
        for (var key in src) {
          if (src.hasOwnProperty(key)) {
            if (!undefinedOnly || dst[key] === void 0) {
              dst[key] = src[key];
            }
          }
        }
      }
      return dst;
    };
    function _random(min, max) {
      return min + Math.floor(Math.random() * (max - min + 1));
    };
    function _getFunctionName(func) {
      var name = 'anonymous';
      if (func === Function || func === Function.prototype.constructor) {
        name = 'Function';
      } else if (func !== Function.prototype) {
        var match = ('' + func).match(/^(?:\s|[^\w])*function\s+([^\(\s]*)\s*/);
        if (match != null && match[1] !== '') {
          name = match[1];
        }
      }
      return name;
    };
    function _Process_getNamedArgument(name, def, min, max) {
      if (WScript.Arguments.Named.Exists(name)) {
        var arg = WScript.Arguments.Named.Item(name);

        // 型が一致する場合、代入する
        if (def === void 0) {                   // 未定義の時
          def = (arg === void 0)? true: arg;
        } else if (typeof def == typeof arg) {  // string or boolean の時
          def = arg;
        } else if (typeof def == 'number') {
          try {
            arg = new Number(arg);
            if (isNaN(arg)) {
            } else if (min !== void 0 && arg < min) {
            } else if (max !== void 0 && arg > max) {
            } else {
              def = arg;
            }
          } catch (e) {}  // 変換失敗
        }
      }
      return def;
    };
    function _getParentElement(element, tag) {
      var callee = _getParentElement;
      if (element === null) {                     // 要素がない時
      } else if (element.tagName === null) {      // 要素がない時
      } else if (element.tagName.toLowerCase() === tag.toLowerCase()) {  // タグ一致
        return element;                           // 要素
      } else {
        return callee(element.parentElement, tag);// 再帰呼び出し(親要素)
      }
      return null;
    }
  }


  // -------------------- static --------------------

  _this = function WebBrowser_constructor() {
    this.initialize.apply(this, arguments);
  };

  _this.READYSTATE_UNINITIALIZED= 0;  // ReadyState: 未完了状態
  _this.READYSTATE_LOADING      = 1;  // ReadyState: ロード中状態
  _this.READYSTATE_LOADED       = 2;  // ReadyState: ロード完了状態、ただし操作不可能状態
  _this.READYSTATE_INTERACTIVE  = 3;  // ReadyState: 操作可能状態
  _this.READYSTATE_COMPLETE     = 4;  // ReadyState: 全データ読み込み完了状態

  /**
   * UserAgent
   * Desktop: IE11(Windows)
   * Mobile:  Firefox(Android)
   * TODO:随時更新が必要
   *      https://developer.mozilla.org/ja/docs/Web/HTTP/Gecko_user_agent_string_reference
   */
  _this.Desktop = 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko';
  _this.Mobile  = 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0';
  _this.UserAgent = _this.Desktop;

  _this.animation = false;      // 待機アニメーションを使用する

  /**
   * 親要素のタグを探す
   * @param {Element} element - 要素
   * @param {string} tag - 探すタグ
   * @return {Element} 親要素
   */
  _this.getParentElement = 
  _this.prototype.getParentElement = function WebBrowser_getParentElement(element, tag) {
    return _getParentElement(element, tag);
  };

  /**
   * IEの読み込みを待機
   * @param {ActiveXObject} ie - InternetExplorer.Application
   * @param {number} [opt_state=3] - 状態(READYSTATE参照)
   * @param {number} [opt_timeout=60000] - 待機時間(ms)
   */
  _this.wait = function WebBrowser_static_wait(ie, opt_state, opt_timeout) {
    var state = (opt_state == null)? _this.READYSTATE_INTERACTIVE: opt_state;
    var timeout = (opt_timeout == null)? 60*1000: opt_timeout;

    if (ie.Busy || (ie.readystate < state)) {   // 重複呼び出し対策
      var begin = new Date().getTime();
      var waiting = null;
      if (Console && Console.getAnimation && _this.animation) {
        // アニメーションスリープ設定
        waiting = Console.getAnimation('WebBrowser.wait');
        if (waiting.created !== true) {
          waiting.delay = 1000;
          waiting.prefix = '';
          waiting.suffix = '';
          waiting.animations = ['   ','.  ','.. ','...'];
          waiting.createAnimeText = function WebBrowser_createAnimeText() {
            return this.prefix+this.animations[this.count%this.animations.length]+this.suffix;
          };
          waiting.created = true;
        }
      }

      if (waiting) {
        waiting.reset();
        waiting.start();
      }
      for (var i=0; i<2; i++) {                 // 安全のため、2回実行
        // busyの場合、あるいは、読み込み中の場合は、100ミリ秒スリープする
        while (ie.Busy || (ie.readystate < state)) {
          WScript.Sleep(100);
          if (begin+timeout < new Date().getTime()) {
            // タイムアウト時間経過
            var message = 'time out. (Busy='+ie.Busy+', readystate='+ie.readystate+')';
            throw (ErrorUtility)? ErrorUtility.create(message): new Error(message);
          }
        }
      }
      if (waiting) {
        waiting.stop();
      }

      // 安全のため、最短300msを確保
      WScript.Sleep(300);
    }
    // 補足:デフォをREADYSTATE_INTERACTIVE(3)とする。
    //      完全な完了は、4であるが、4にならないページ等もあり、(例:楽天)
    //      3でも問題が起こらないことも多いため、デフォは3とする。
    //      4が必須の場合、明示的に再度wait(4)しなおすこと。
  };

  /**
   * 関数実行
   * @param {ActiveXObject} ie - InternetExplorer.Application
   * @param {Function} func - 実行する関数
   */
  _this.js = function WebBrowser_js(ie, func) {
    _this.wait(ie);     // 安全のため、待機

    // 動的実行
    var d = ie.document;
    var e = d.createElement('script');
    e.type = 'text/javascript';
    e.text = '('+func.toString()+')();';
    if (d.head) {       d.head.appendChild(e);  }
    else if (d.body) {  d.body.appendChild(e);  }
    else {
      var message = 'append failed. (head='+d.head+', body='+d.body+')'
      throw (ErrorUtility)? ErrorUtility.create(message): new Error(message);
    }
    // 補足:setTimeoutと異なり、HTML上に書き込むため、
    //      他のスクリプトに干渉する可能性がある

    _this.wait(ie);     // 安全のため、待機
    // 補足:ブックマークレット(Navigateのurlとして指定)とすると、
     //     wait()で読み込みが完了しないため、本方法を使用する。
  };

  /**
   * CSS追加
   * @param {ActiveXObject} ie - InternetExplorer.Application
   * @param {string} text - 追加するコード
   */
  _this.css = function WebBrowser_css(ie, text) {
    _this.wait(ie);

    var d = ie.document;
    var e = d.createElement('style');
    e.type = 'text/css';
    e.styleSheet.cssText = text;
    if (d.head) {       d.head.appendChild(e);  }
    else if (d.body) {  d.body.appendChild(e);  }
    else {
      var message = 'append failed. (head='+d.head+', body='+d.body+')'
      throw (ErrorUtility)? ErrorUtility.create(message): new Error(message);
    }

    _this.wait(ie);
  };

  /**
   * ページ遷移後の共通処理
   * @param {ActiveXObject} ie - InternetExplorer.Application
   * @param {string} confirm - confirmの戻り値
   * @param {string} confirm - promptの戻り値
   */
  _this.postNavigate = function WebBrowser_static_postNavigate(ie, confirm, prompt) {
    var ret = false;
    try {
      // 自動化の妨げとなる関数を上書き
      var func = ''
            + 'window.alert = function (message) {console.log("alert(): "+message);};'
            + 'window.onunload = function () {console.log("onunload()");};'
            + 'window.onbeforeunload = function () {console.log("onbeforeunload()");};';
      if (confirm === void 0) { confirm = true;  }      // デフォでOKとする
      if (confirm !== null) {                           // 標準指定(null)でない時
        func += ''
            + 'window.confirm = function () {'
            + '  console.log(\'confirm('+confirm+')\');'
            + '  return('+confirm+');'
            + '};';
      }
      if (prompt === void 0) { prompt = '""';  }        // デフォで空文字とする
      if (prompt !== null) {                            // 標準指定(null)でない時
        func += ''
            + 'window.prompt = function () {'
            + '  console.log(\'prompt('+prompt+')\');'  // ダブルクオートの2重対応
            + '  return('+prompt+');'
            + '};';
      }
      _this.js(ie, new Function(func));
      // 補足:onload以前は、対象外(Seleniumでも無理っぽいので諦めたほうが良さそう)

      _this.wait(ie);   // 安全のため、待機
      ret = true;
    } catch (e) {}      // エラーは、無視
    return ret;
  };


  // -------------------- local --------------------

  /**
   * コンストラクタ
   * @constructor
   */
  _this.prototype.initialize = function WebBrowser_initialize(param) {
    if (!param) { param = {}; }

    this.ie = null;     // IE
    this.console = Console && Console.getConsole();     // 出力コンソール
    this.setup = {};    // IEの設定

    // 初期化後の設定 > コマンドライン引数 > コンストラクタ引数 > 初期値
    _extend(this.setup, param);
    _extend(this.setup, {
        debug:false,            // デバッグモード
        visible:true,           // 表示有無
        top:-1, left:-1, width:-1, height:-1,   // 表示位置
        headers:void 0,         // ヘッダー
        confirm:true,           // ダイアログ応答の戻り地
        prompt:'""',            // ダイアログ応答の入力文字列
        sleep: {
          min: 5*1000,
          max: 10*1000
        },
        cache: {                // キャッシュ保持
          querySelectorAll:false,// querySelectorAllを収集する
          max: 25               // 最大キャッシュ保持数
        }
    }, true);

    this.clearCache();
    this.addCacheSeparator();
  };

  /**
   * デストラクタ
   * IEを終了する(必須ではない)。
   * 本関数を呼び出さないまま、プログラムを終了すると、IEが起動したままとなる。
   * @destructor
   */
  _this.prototype.quit = 
  _this.prototype.Quit = function WebBrowser_Quit() {
    this.addCacheSeparator();
    if (this.ie !== null) {
      // 終了しない方法も必要?(必要になってから考える)
      this.ie.Quit();
      this.ie = null;
      this.url = '';
    }
  };

  /**
   * コマンドライン引数の解析
   */
  _this.prototype.commandLineArgumentAnalysis = function WebBrowser_commandLineArgumentAnalysis() {
    this.addCacheSeparator();
    this.setup.debug   = _Process_getNamedArgument('debug', this.setup.debug);
    this.setup.visible = _Process_getNamedArgument('visible', this.setup.visible);
    this.setup.top     = _Process_getNamedArgument('top', this.setup.top, 0);
    this.setup.left    = _Process_getNamedArgument('left', this.setup.left, 0);
    this.setup.width   = _Process_getNamedArgument('width', this.setup.width, 1);
    this.setup.height  = _Process_getNamedArgument('height', this.setup.height, 1);
  };

  /**
   * キャッシュ関連
   * TODO: 外部に切り出したい
   * @deprecated 仕様変更の可能性大
   */
  function getCallerFunctionName() {
    return (ErrorUtility)?
        ErrorUtility.trace(3, true):                    // 関数名 + 引数
        _getFunctionName(_getCallee().caller.caller);   // 関数名のみ
  };
  _this.prototype.clearCache = function WebBrowser_clearCache() {
    this.cache = null;
    this.cache = [];
    this.addCacheSeparator(JSON.stringify(this.setup));
  };
  _this.prototype.addCache = function WebBrowser_addCache(cacheData, var_args) {
    var catchArray = [];
    for (var i=0; i<arguments.length; i++) {
      catchArray.push(arguments[i]);
    }
    if (this.setup.cache) {
      this.cache.push(catchArray);
      if (this.setup.cache.max != -1 && this.setup.cache.max < this.cache.length) {
        this.cache.shift();
      }
    }
    if (this.console && this.setup.debug === true) {
      for (var i=0; i<catchArray.length; i++) {
        if (i != 0) {  this.console.print(', ');  }
        this.console.print(catchArray[i]);
      }
    }
  };
  _this.prototype.addCacheEnd = function WebBrowser_addCacheEnd(cacheData, var_args) {
    if (this.setup.cache) {
      Array.prototype.push.apply(this.cache[this.cache.length-1], arguments);
    }
    if (this.console && this.setup.debug === true) {
      for (var i=0; i<arguments.length; i++) {
        this.console.print(', ');
        this.console.print(arguments[i]);
      }
    }
  };
  _this.prototype.addCacheSeparator = function WebBrowser_addCacheSeparator(message) {
    var carcheArray = [getCallerFunctionName()];
    if (message) {  carcheArray.push(message);  }
    carcheArray.push(this.getURL());
    this.addCache.apply(this, carcheArray);
    if (this.console && this.setup.debug === true) {
      this.console.println();
    }
  };
  _this.prototype.getCacheData = function WebBrowser_getCacheData() {
    var msgs = [];
    for (var i=0; i<this.cache.length; i++) {
      msgs.push(this.cache[i].join(', '));
    }
    return msgs.join('\n');
  };

  /**
   * 前処理
   */
  _this.prototype.init = function WebBrowser_init() {
    if (this.ie != null) {
      // IEが動作するか確認
      // 動作しない場合、終了する
      try {
        this.ie.document.querySelector('body');
      } catch (e) {
        try {
          this.ie.Quit();
        } catch (e2) {}
        this.ie = null;
      }
    }
    if (this.ie === null) {
      // IEの作成
      // 2回作成を試みる
      for (var i=1; i>=0; i--) {
        try {
          this.ie = new ActiveXObject('InternetExplorer.Application');
          break;
        } catch (e) {
          if (i === 0) {  throw (ErrorUtility)? ErrorUtility.captureStackTrace(e): e;  }
        }
        if (this.console && this.console.sleep && _this.animation) {
          var anime = Console.getAnimation('WebBrowser.init', 'Console.countdown');
          if (anime.created !== true) {
            anime.created = true;
          }
          anime.reset(30*1000);
          anime.exec();
        } else {
          WScript.Sleep(30*1000);
        }
      }

      // IE初期化
      if (this.setup.top >= 0) {  this.ie.Top   = this.setup.top;   }
      if (this.setup.left >= 0) { this.ie.Left  = this.setup.left;  }
      if (this.setup.width > 0) { this.ie.Width = this.setup.width; }
      if (this.setup.height > 0) {this.ie.Height= this.setup.height;}
      this.ie.Visible = this.setup.visible;
      this.ie.Navigate('about:blank');  // 空ページ表示
    }
    _this.wait(this.ie);
    this.url = this.getURL();

    // キャッシュ(コマンド保存)
    this.addCache(getCallerFunctionName());
    // 補足:読み込み完了状態で処理に進む
    //      読み込み中に、以降の処理を行うとエラーとなるため
  };

  /**
   * 後処理
   * @param {(boolean|number)} ret - 結果(成功(true)の場合、待機する。キャッシュに保存する)
   * @param {boolean} sleep - 待機有無(falseでないければ、待機する)
   */
  _this.prototype.finish = function WebBrowser_finish(ret, sleep) {
    _this.wait(this.ie);

    // キャッシュ(結果保存)
    this.addCacheEnd(ret, this.getURL());
    if (this.console && this.setup.debug === true) {
      this.console.println();
    }

    // ランダムスリープ(人間ぽさを演出するため)
    if (ret !== false) {
      var min = this.setup.sleep.min;
      var max = this.setup.sleep.max;
      if (sleep !== false && min && max) {
        var random = _random(min, max);
        if (this.console && this.console.sleep && _this.animation) {
          var anime = Console.getAnimation('WebBrowser.finish', 'Console.countdown');
          if (anime.created !== true) {
            anime.created = true;
          }
          anime.reset(random);
          anime.exec();
        } else {
          WScript.Sleep(random);
        }
      }
    }
    this.url = this.getURL();
  };

  /**
   * IEを取得
   * 取得した、IEを操作してもキャッシュに保存しない。
   * @return {ActiveXObject} IE
   */
  _this.prototype.getIE = function WebBrowser_getIE() {
    return this.ie;
  };

  /**
   * URLを取得
   * 現在表示中のURLを取得する。
   * @return {string} URL
   */
  _this.prototype.getURL = function WebBrowser_getURL() {
    return (this.ie !== null)? this.ie.LocationURL: '';
  };

  /**
   * Documentを取得
   * 取得した、ドキュメントを操作してもキャッシュに保存しない。
   * @return {Element} document
   */
  _this.prototype.getDocument = function WebBrowser_getDocument() {
    return (this.ie !== null)? this.ie.Document: null;
  };

  /**
   * 設定を保存
   * 本クラスが使用する設定を保存する。
   * 設定は、キャッシュに書き出す。
   * @param {string} name - 名称
   * @param {*} value - 値
   */
  _this.prototype.setSetupValue = function WebBrowser_setSetupValue(name, value) {
    this.addCacheSeparator();
    this.setup[name] = value;
  };
  _this.prototype.setHeaders = function WebBrowser_setHeaders(header, var_args) {
    var headers = '';
    for (var i=0; i<arguments.length; i++) {
      headers += arguments[i]+'\n';
    }
    this.setSetupValue('headers', headers);
  };

  _this.prototype.setConfirmAnswer = function WebBrowser_setConfirmAnswer(answer) {
    this.setSetupValue('confirm', answer);
  };

  _this.prototype.setPromptAnswer = function WebBrowser_setPromptAnswer(answer) {
    this.setSetupValue('prompt', JSON.stringify(answer));
  };

  _this.prototype.setRandomSleep = function WebBrowser_setRandomSleep(min, max) {
    this.setSetupValue('sleep', {min:min, max:max});
  };

  /**
   * IEの読み込みを待機
   * @param {number} state - 状態(READYSTATE参照)
   * @param {number} timeout - 待機時間(ms)
   */
  _this.prototype.wait = function WebBrowser_wait(state, timeout) {
    _this.wait(this.ie, state, timeout);
  };
  _this.prototype.waitUninitialized = function WebBrowser_waitUninitialized(timeout) {
    this.wait(_this.READYSTATE_UNINITIALIZED, timeout);
  };
  _this.prototype.waitLoading = function WebBrowser_waitLoading(timeout) {
    this.wait(_this.READYSTATE_LOADING, timeout);
  };
  _this.prototype.waitLoader = function WebBrowser_waitLoader(timeout) {
    this.wait(_this.READYSTATE_LOADED, timeout);
  };
  _this.prototype.waitInteractive = function WebBrowser_waitInteractive(timeout) {
    this.wait(_this.READYSTATE_INTERACTIVE, timeout);
  };
  _this.prototype.waitComplete = function WebBrowser_waitComplete(timeout) {
    this.wait(_this.READYSTATE_COMPLETE, timeout);
  };

  _this.prototype.postNavigate = function WebBrowser_postNavigate(opt_confirm, opt_prompt) {
    var confirm = (opt_confirm != null)? opt_confirm: this.setup.confirm;
    var prompt = (opt_prompt != null)? opt_prompt: this.setup.prompt;

    return _this.postNavigate(this.ie, confirm, prompt);
  };

  /**
   * 閲覧する
   * 既に閲覧中の場合、再閲覧しない。
   * @param {string} url - URL
   * @param {number} flags - ウィンドウ詳細設定
   * @param {string} targetFrameName - ウィンドウ表示設定
   * @param {string} postData - POSTデータ設定
   * @param {string} headers - HTTPヘッダー
   * @return {boolean} 成否
   */
  _this.prototype.Navigate = 
  function WebBrowser_Navigate(url, flags, targetFrameName, postData, headers) {
    var ret = false;
    if (this.getURL() != url) {
      ret = this.doNavigate.apply(this, arguments);
    }
    return ret;
  };

  /**
   * 強制的に閲覧する
   * 既に閲覧中の場合、再閲覧する。
   * @param {string} url - URL
   * @param {number} flags - ウィンドウ詳細設定
   * @param {string} targetFrameName - ウィンドウ表示設定
   * @param {string} postData - POSTデータ設定
   * @param {string} headers - HTTPヘッダー
   * @return {boolean} 成否
   */
  _this.prototype.doNavigate = 
  function WebBrowser_doNavigate(url, flags, targetFrameName, postData, headers) {
    headers = (headers !== void 0)? headers: this.setup.headers;

    this.init();

    this.ie.Navigate(url, flags, targetFrameName, postData, headers);
    var ret = this.postNavigate();

    this.finish(ret);
    return ret;
  };

  function query2element(element, query) {
    if (_isString(query)) {             // 文字列(クエリー)
      return element.querySelector(query);
    } else if (_isElement(query)) {     // 要素
      return query;
    }
    return null;
  };

  function query2elements(element, query) {
    if (_isString(query)) {             // 文字列(クエリー)
      return element.querySelectorAll(query);
    } else if (_isArray(query)) {
      return query;
    } else if (_isElement(query)) {     // 要素
      return [query];
    }
    return [];
  };

  /**
   * アンカー(href)を閲覧
   * targetをクリアする。(_blankがあると別ウィンドウを表示するため)
   * @param {(string|Element)} element - 要素
   * @param {number} [number=0] - 要素番号
   * @return {boolean} 成否
   */
  _this.prototype.doAnchor = function WebBrowser_doAnchor(elements, number) {
    var ret = false;

    this.init();

    elements = query2elements(this.ie.document, elements);
    if (elements.length != 0) {
      if (number == null) {
        number = 0;
      } else if (number < 0) {
        number = random(0, elements.length-1);
      }
      var element = elements[number%elements.length];
      this.addCacheEnd(number%elements.length);

      var a = _getParentElement(element, 'a');
      if (a !== null) {
        // 親アンカーのターゲットを削除(_blank対策)
        a.target = '';
      }

      if (a !== null && a.href) {
        this.addCacheEnd('navi');
        this.ie.Navigate(a.href, null, null, null, this.setup.headers);
      } else {
        this.addCacheEnd('click');
        element.click();
        // 補足:onclickは、アンカーにかぎらずあるため、elementをクリックする
      }
      ret = this.postNavigate();
    }

    this.finish(ret);
    return ret;
  };

  /**
   * 要素をクリックする
   * 補足:ヘッダ偽装未対応
   * @param {(string|Element)} elements - 要素
   * @param {number} [number=0] - 要素番号
   * @return {boolean} 成否
   */
  _this.prototype.doClick = function WebBrowser_doClick(elements, number) {
    var ret = false;

    this.init();

    elements = query2elements(this.ie.document, elements);
    if (elements.length != 0) {
      if (number == null) {
        number = 0;
      } else if (number < 0) {
        number = random(0, elements.length-1);
      }
      var element = elements[number%elements.length];
      this.addCacheEnd(number%elements.length);

      // 親アンカーのターゲットを削除(_blank対策)
      var a = _getParentElement(element, 'a');
      if (a !== null) {
        a.target = '';
      }

      element.click();
      ret = this.postNavigate();
    }

    this.finish(ret);
    return ret;
  };

  /**
   * formに入力してからsubmitする
   * @param {(string|Element)} form - 要素
   * @param {Object} params - パラメータ
   * @param {(string|Element)} [submit=null] - 確定要素(formからのクエリーでも可)
   * @return {boolean} 成否
   */
  _this.prototype.submit = 
  _this.prototype.doSubmit = function WebBrowser_doSubmit(form, params, submit) {
    var ret = false;

    this.init();

    form = query2element(this.ie.document, form);
    if (form !== null) {
      // パラメータ設定
      if (params != null) {
        for (var key in params) {
          if (params.hasOwnProperty(key)) {
            form[key].value = params[key];
          }
        }
        WScript.Sleep(500);  // 安全のため、待機(テスト時の目視もしやすい)
      }
      // サブミット
      submit = query2element(form, submit);
      if (submit != null) {                     // submit指定あり時
        this.addCacheEnd('arg');
        submit.submit();
      } else if (_isElement(form.submit)) {     // submitが要素の時
        this.addCacheEnd('submit');
        form.submit.click();
        // name='submit'の場合、form.submit()できないため、代替処置
      } else {
        this.addCacheEnd('form');
        form.submit();
      }
      ret = this.postNavigate();
    }

    this.finish(ret);
    return ret;
    // 補足:nameがsubmitの要素がある場合、form.submit()が上書きされているため、注意
  };

  /**
   * querySelectorAll
   * @param {string} query - クエリ
   * @param {Element} [element=document] - 親要素
   * @return {Element[]} 要素配列
   */
  _this.prototype.querySelectorAll = function WebBrowser_querySelectorAll(query, element) {
    if (this.setup.cache && this.setup.cache.querySelectorAll === true) {
      this.init();
    }

    var elements = (element)? 
                      element.querySelectorAll(query):
                      this.ie.document.querySelectorAll(query);

    if (this.setup.cache && this.setup.cache.querySelectorAll === true) {
      this.finish(elements.length, false);
    }
    return elements;
  };

  _this.prototype.querySelector = function WebBrowser_querySelector(query, element) {
    var elements = this.querySelectorAll(query, element);
    return (elements.length) ? elements[0]: null;
  };

  return _this;
});

変更履歴

更新日内容
2018/07/18v1 - add - 初版
2019/08/23v2 - update - doNavigateを追加
2019/08/23v2 - update - アニメーション関連をリファクタリング
2019/08/23v2 - update - コマンドライン引数を自動で確認しないように変更