ラベル web の投稿を表示しています。 すべての投稿を表示
ラベル web の投稿を表示しています。 すべての投稿を表示

2017年4月5日水曜日

Chrome extentionを使い、iframe内外でデータ通信

◆ 目的
Webページ(親ページ)とそこからiframeで読み込んだ子ページ間でデータの授受を行う。
ただし、iframe内のコンテンツは親ページのドメイン外に存在する任意のサイトとする。



◆ 課題
解決する課題は2つある。
1. クロスドメイン制約への対応
XMLHttpRequestを投げる際などにも必ず気に掛ける点だろう。
Same Origin Policy(同一生成元ポリシー)を回避しなければならない。

今回は任意の外部サイトをiframeに取り込むことを前提としているため、
CORS(Cross-Origin Resource Sharing)もJSONP(JSON with padding)も今回は使えない。

2. iframe内での読み込み不許可ヘッダへの対応
HTTPのレスポンスヘッダに、iframe内からWebページが読み込まれるのを防止するオプション(X-Frame-Options)が付与されていればiframe内で表示はできない。



◆ 解決策
1. postMessageの利用
クロスドメイン制約への対応として、HTML5で用意されたpostMessageの利用を思いつく。

(親サイト)
<iframe id="ifrm" src="外部サイト"></iframe>

<script type="text/javascript">
window.onload = function() {
  var ifrm = document.getElementById('ifrm').contentWindow
  ifrm.postMessage("hello", '外部サイト')
};
</script>

(外部サイト)
<script type="text/javascript">
window.addEventListener('message', function(event) {
    alert(event.data)
}, false);
</script>

しかし、Chromeが許可しない。
※Safariでは警告なく、実施できた。

(エラー例)
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://親サイト') does not match the recipient window's origin ('http://外部サイト').


2. iframeを使わない
iframeを使う前提を変え、コンテンツをダウンロードし、外部サイトを親側で再現させる。つまり、前提を変える。
が、しかし、JavaScriptが生成する動的ページのリソースの管理は困難があるため、やはり、iframeは使いたい。前提は戻す。


3. Google Chromeの拡張機能の利用
Chromeに限定されるが、Chromeの拡張機能を使えば対応ができそうである。
データの共有は、バックグラウンドで動作させるスクリプト内でセッションストレージを使えばいいだろう。
また、ヘッダの書き換えも実施できる。



◆ 拡張コード概要
chrome拡張を利用することにした。
拡張コードを有効にするには、最低限以下の3つのファイルを用意し、それらを適当なフォルダに入れ、Chromeブラウザの拡張機能からインポートすれば良い。

○ マニフェスト ファイル
拡張機能に関する情報を与える。

○ コンテンツ スクリプト
ブラウザで表示させるページで読み込むjsとは別空間で実行させるjsである。
このファイルは親サイトと、iframe内の外部サイト、両方に読み込まれる。
空間は分かれているため、コンテンツスクリプト内で利用しているjQueryなどのライブラリがサイトで利用しているバージョンと異なっていても問題は起きない。

○ バックグラウンド スクリプト
Chromeのバックエンド側で処理させるjsである。
表示コンテンツには取り込まれないが、コンテント ファイルとの間でメッセージ通信ができる。


簡易図で表すと下記のような感じである。
   parent
+----------+
|          |← contentScripts.js
|  iframe  |                       
|  +----+  |
|  |    |  |
|  |    |←-|-- contentScripts.js
|  +----+  |
|          |
+----------+
background.js

contentScripts.js、background.jsの名称はmanifest.json内で指定する。



◆ 試験
1. iframe内でマウスを操作。マウスオーバしたタグ要素に色がつくようにしている。
  そのタグ要素でクリックすると、タグ名がセッションストレージへ保存される。

2. 親側でiframe外の要素をクリックする。
  iframeで取得した要素がalert表示されれば成功である。



◆ コード例
○ manifest.json


○ contentScripts.js


○ background.js



◆ コード解説
1点、解説を加えておく。
contentScript.jsは親子両方に読み込まれるため、共通処理以外のjsコードは、親子用で条件を加えている。
// iframe用
if (window != parent) {
  ~snip~
} // 親用
else{
  ~snip~
}

2016年3月24日木曜日

Capybaraを使ってブラウザ操作を自動化


◆ 目的
モダンでリッチな動的WEBインターフェースをコマンド経由で自動操作する。
(WEBに限らず、GUI画面全般を画像認識技術を使いオートメーション化したい場合はこちら)



◆ 対象
試験対象のWEBをIBMのBluemix管理コンソールとした。
https://console.ng.bluemix.net/

個人的に利用することが多く、また定期的に課金情報を把握する必要性があるため、そのチェックを自動化したいと思ったことが動機である。
リッチなインターフェースであるため、このサイトで処理を自動化できれば大抵のサイトでも手順を応用できるだろう。


(乗り越えるべき技術的ポイント)
1. IDとパスワードを使ってログイン

2. 個人設定を表示させるアイコンをクリック
 既存画面の前面へポップアップ画面が表示される
 その管理画面で数か所の項目を選択

3. 課金情報画面へ遷移し、閉じられているサービス単位の課金項目をすべて開示
   (右三角ボタン(▷)を押すと矢印の向きが下(▿)になり課金情報が開く)

1は一般的なフォーム操作である。
難しい部分は2、3だろう。
HTMLが静的ではなく、トリガーイベントにより生成される点である。
ある対象上でマウス操作などをすることでトリガーを発生させ、その動作からできあがったHTMLファイルを操作しなくてはならない。



◆ 利用する道具
言語はRubyを使い、WebのUIテストフレームワークであるCapybaraを利用する。

ブラウザ試験用途の道具としては種々のものがあるがCapybaraを使えば、好みのブラウザをドライバとして指定するだけで共通的な操作で扱うことができる。

・Selenium [ブラウザ利用]
・RackTest [ブラウザシミュレータ]
・Webkit [ブラウザエンジン]
・Poltergeist(PhantomJS) [ブラウザシミュレータ]


またRubyのDSLテストフレームワークとして以下のようなものが有名だろう。
今回は試験用コードは書かないが、テストする場合もCapybaraと親和性高く組み合わせることができる。
・Rspec
・Test::Unit
・MiniTest::Spec
・Cucumber



◆ 事前準備
(Capybaraのインストール)
gem経由で入れる。
$ gem install capybara


(Poltergeist(PhantomJS)のインストール)
先にいくつか試験で使えそうなブラウザを紹介したが、今回はPoltergeist(PhantomJS)を使う。
ブラウザを起動せず(GUIのないヘッドレスブラウザ)、JavaScriptが実行できることからこちらを選んだ。

導入はredhat系OSでは以下の通り。
多少時間がかかる。

$ sudo yum -y install gcc gcc-c++ make flex bison gperf ruby openssl-devel freetype-devel fontconfig-devel libicu-devel sqlite-devel libpng-devel libjpeg-devel

$ cd /usr/local/src/

$ git clone --recurse-submodules git://github.com/ariya/phantomjs.git

$ cd phantomjs

$ ./build.py

$ sudo ln -s /usr/local/src/phantomjs/bin/phantomjs /usr/bin/phantomjs


(nokogiriのインストール)
html解析用にnokogiriも入れておく。
$ gem install nokogiri



◆ クローリングコード


2014年11月2日日曜日

Ajaxを使ったPOSTメソッドによるHTTP通信

◆ 目的
Ajaxの非同期通信を使ったPOSTメソッドによるHTTP通信を実現する。
今更な感があるが、jQueryなどのライブラリを利用する機会が多くなったため、
少しレイヤを下げて、改めて、JavaScriptとDOM(Document Object Model)の
コードを組み合わせた簡易なWEBアプリケーションを構築したくなったしだい。



◆ 動作概要
クライアントが入力フォーム(nameとage)を入力しsendボタンを押すと、
サーバへ非同期にPOST通信を発生させる。
サーバ側がフォームの内容をCGIで処理して実行結果をクライアントへ返答。
サーバからの応答はHTMLコード下段の部分に挿入する。
ページが遷移しないところがポイントである。



◆ HTMLコード
※サーバ設定がないため(CGIが実行できないので)、動作はしない

POST TEST

sample

send form

name:
age:




returning data

name: -
age: -


(実HTML)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>POST TEST</title>
<link href="post.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="post.js"></script>
</head>
<body>
<h1>POST TEST</h1>
<p>sample</p>
<hr />
<h2>send form</h2>
<form action="post.rb" id="sendForm">
  <div>
    name:
    <input type="text" id="name" name="name" size="30" autocomplete="off" />
  </div>
  <div>
    age:
    <input type="text" id="age" name="age" size="3" autocomplete="off" />
  </div>
  <div>
    <input type="button" name="sendBtn" id="sendBtn" value=“send” />
  <div>
</form>
<hr />
<h2>returning data</h2>
<div>name: <span id="responseName">-</span></div>
<div>age: <span id="responseAge">-</span></div>
</body>
</html>



◆ CGIコード 
#!/usr/bin/ruby
require "cgi"
cgi = CGI.new

name = cgi["name"]
age = cgi['age']

response  = "name\t#{name}\n"
response += "age\t#{age}\n"

puts cgi.header
#puts "Content-Type: text/plain; charset=utf-8";
print "\n"
print response



◆ JavaScriptコード
簡単なコードなので説明は省略。
一部利用しない関数を断りを入れて書いているが、
これはただの自分用メモである。

var httpObj;
var timerId;
var timeoutSec = 10;

function printResData(textData) {
  var i;
  var lines = textData.split("¥n");
  var responseName = document.getElementById('responseName');
  var responseAge  = document.getElementById('responseAge');

  for(i = 0; i < lines.length; i ++) {
    var parts = lines[i].split("¥t");
    var name = parts[0];
    var value = parts[1];
    if(name == 'name') {
        responseName.childNodes[0].nodeValue = value;
    } else if(name == 'age') {
        responseAge.childNodes[0].nodeValue = value;
    }
  }

  document.getElementById('sendBtn').disabled = false;
}


function postData(e) {
  document.getElementById('sendBtn').disabled = true;

  var name = document.getElementById('name').value;
  var age  = document.getElementById('age').value;

  var postData;
  postData = 'name=' + encodeURIComponent(name) +
              '&age=' + encodeURIComponent(age);

  var targetUrl = 'post.rb';

  httpPostRequest(targetUrl, postData, printResData);
}


function httpPostRequest(targetUrl, postData, funcitonReference) {
  try {
    if(window.XMLHttpRequest) {
        httpObj = new XMLHttpRequest();
    } else {
        httpObj = false;
    }
  } catch(e) {
      httpObj = false;
  }
  if(! httpObj) {
    fail();
  }

  timerId = setInterval('timeoutCheck()', 1000);

  httpObj.open("POST", targetUrl, true);
  httpObj.onreadystatechange = function() {
    if (httpObj.readyState == 4) {
      clearInterval(timerId);
      if (httpObj.status == 200) {
        funcitonReference(httpObj.responseText);
      } else {
        alert(httpObj.status + ' : ' + httpObj.statusText);
        return false;
      }
    }
  }
  httpObj.send(postData);
}


function fail() {
  alert("error: your browser is not supported");
  return false;
}


function timeoutCheck() {
  timeoutSec --;
  if(timeoutSec <= 0) {
    clearInterval(timerId);
    httpObj.abort();
    alert('error: timeout');
    return false;
  }
}


function setListeners(e) {
  var sendBtn = document.getElementById('sendBtn');
   // true: capturing(from top), false: bubbling(from bottom)
  addListener(sendBtn, 'click', postData, false);
}


function addListener(elem, eventType, func, cap) {
  if(elem.addEventListener) {
    elem.addEventListener(eventType, func, cap);
  } else {
    fail();
    return false;
  }
}


/* 以下はここでは使わない
function removeListener(elem, eventType, func, cap) {
  elem.removeEventListener(eventType, func, cap);
}


function getElemPos(elem) {
  var obj = new Object();
  obj.x = elem.offsetLeft;
  obj.y = elem.offsetTop;
  while(elem.offsetParent) {
    elem = elem.offsetParent;
    obj.x += elem.offsetLeft;
    obj.y += elem.offsetTop;
  }
  return obj;
}

function getTargetNode(e) {
  var targetNode = e.target;
  return targetNode;
}

function stopDefaultAction(e) {
  e.preventDefault();
}


function stopPropagation(e) {
  e.stopPropagation();
}
*/


addListener(window, 'load', setListeners, false);

2014年9月23日火曜日

Seleniumを利用してWEBの操作を自動化

※追記
Seleniumを直叩きするより、Capybaraを通して実行した方がいいかもしれない。
http://alpha-netzilla.blogspot.jp/2016/03/crawler.html



目的
Seleniumを利用してWEBのGUI操作(UI:User Interface部分)を自動化する。
※GUIでのオペレーションを自動化するためにSikuliも最近ではよく使われる。
ただし両者は実装も利用用途も異なるため使い道は見極める必要がある。



環境、事前準備
・ Rubyのインストール
今回はRubyからSeleniumを操作するため、Rubyを使えるようにしておくこと。

インストール後、windows環境であれば以下からパスを指定する。
コントールパネル ->
システムの詳細設定 ->
詳細設定 ->
環境変数 ->
ユーザの環境変数 ->
変数 ->
Path   C:\Users\user\Ruby**\bin


・ Selenium Webdriverのインストール
SeleniumのWebDriverをRubyへ組み込む。
gemを使ってパッケージングされたライブラリを入れてしまえばよい。
# gem install selenium-webdriver


・ Selenium IDE(Firefoxのプラグイン)のインストール
GUIでのマウス操作を記録させるために便利である。
Selenium IDEの項にダウンロードページがある。

ダウンロード後、Firefoxを起動させ、Selenium IDEをアドオンとしてインストールさせておく。


・ ChromeDriverのインストール
ChromeDriverとはwire protocolを実装したスタンドアローンサーバである。
Chromeがインストールされていないと利用できない。

ダウンロード元と、実行ファイルの置き場所は公式マニュアルを参照。


・ ヘッドレスブラウザの用意
ツールを実行すると指定したChromeなり、Firefoxのブラウザが立ち上がる。
ブラウザを立ち上げないで試験を実施する必要性が出る場合もあるだろう。
PhantomJSというヘッドレスブラウザを使えば、実ブラウザの立ち上げを回避できる。
WebKitをベースとしているため、実ブラウザと機能面でほぼ遜色はない。



事前知識
WEBの要素をたどり、それをコード化していくのだが、
HTML、XMLのデータツリーノードをどうやって調べればよいだろうか。

簡便な手段は大きく2通りある。

1. Firefoxのプラグインとして組み込んだ、Selenium IDEを使う方法
firefoxを起動
ツール ->
Launch Selenium Bulder ->
Selenium 2
※Record mouseoversのチェックは必要がなければ外しておく

操作が終われば、
Stop recording ->
File ->
Export ->
Ruby

これだけでRubyコードが生成される。


2. Chromeブラウザのデベロッパーツールの利用
特別なモジュールのインストールは不要である。

ブラウザ上で右クリック ->
要素を検証 ->
Elements内のコードをマウスでたどれば
WEB画面上の要素を網掛けしてくれる。

マウスで操作する要素のXPathを知りたければ、
コード上で右クリック ->
Copy XPath
で分かる。


1は楽だが複雑な操作をキャプチャする場合に痒いところに手が届きにくいところがある。
そういった場合に2の手順でHTMLを解析していく、という使い方がいいだろうか。



コード例
どのようにコードを記述するかは見てもらえればすぐにわかるだろう。
html(xml)を例示しながらそれを操作する方法を記載する。
### モジュール読み込み
require 'Rubygems'
require 'Selenium-webdriver'


### ブラウザ起動(3つ目は実ブラウザがなくても実行可能)
wd = Selenium::WebDriver.for :chrome
# wd = Selenium::WebDriver.for :firefox
# wd = Selenium::WebDriver.for :phantomjs


### URL参照(下記どちらでもよい)
wd.navigate.to "http://example.com/"
wd.get "http://example.com/"


### フォーム操作(選択、文字列のクリア、そして文字列入力)
# <input id="account" name='account' type='text'>account</input>
# <input id="password" name='password' type='text'>password</input>

wd.find_element(:id, "account").click
wd.find_element(:id, "account").clear
wd.find_element(:id, "account").send_keys("myaccount")
wd.find_element(:id, "password").click
wd.find_element(:id, "password").clear
wd.find_element(:id, "password").send_keys("mypassword")


#### リンク操作
## 要素(element)で指定する場合
# <div id="entrance">
#   <ul class="home">
#     <li><a href="/login">LOGIN</a></li>
#   </ul>
# </div>

wd.find_element(:link, 'LOGIN').click
# or 
wd.find_element(:link_text, 'LOGIN').click

## xpathで属性(attribute)を指定する場合
wd.find_element(:xpath, '//a[@href='/login']').click

## CSSセレクタで指定する場合
wd.find_element(:css, #entrance ul.home).click


### ファイルのアップロード
# <input type="file" name="data">
wd.find_element(:name, "data").send_keys("c:/hoge.txt")


### インラインフレームの切りかえ
# <iframe class="frame" src="/frame"></iframe>
wd.switch_to.frame(wd.find_element(:class,"frame"))


### ウインドウの切り替え
# <a href="new_window.html" target="_blank">new window</a>
wd.switch_to.window(wd.window_handles.last)


### ウインドウを閉鎖(ウインドウの操作権を先に戻しておく)
wd.switch_to.window(wd.window_handles.last)
execute_script("return window.close();")


### マウス移動
el = wd.find_element(:id, "some_id")
wd.action.move_to(el).perform


### クリック
el = wd.find_element(:id, "some_id")
wd.action.click(el).perform
# or
wd.action.double_click(el).perform


### ドラッグアンドドロップ
el1 = wd.find_element(:id, "some_id1")
el2 = wd.find_element(:id, "some_id2")
wd.action.drag_and_drop(el1, el2).perform


### 警告、案内等のポップアップダイアログへの対処
alert = wd.switch_to.alert()
alert.accept
# or 
alert.dismiss()


### 終了処理
wd.quit


参考
WebDriver: Advanced Usage

2014年6月27日金曜日

パズル系CAPTCHAを自動で操作


 パズル型CAPTCHAとは何か
WEBでの認証時に、CAPTCHA(キャプチャ)として歪んだ文字・数字列の
入力を促されるものの、非常に読み辛く、何度も間違えてしまうという体験は誰にでもあるだろう。
また、その利用勝手の悪さにより、サイトを利用したいという意思のあったユーザの10%以上がサイトを離れるという、サービスの提供側にもデメリットがあるようだ。

利用ユーザとサービス提供者の不満を解消するために、人に優しいパズル型のCapy CAPTCHAというものが各種サイトで使われ始めている。


(パズル型CAPTCHAの簡易例)
右側の△はマウスで動かすことができる。
それを左側の穴が開いた▲のところまでドラッグすると認証成功となる。

1. 埋め込むための画像とそこへ移動させる穴の開いた画像が存在
▲   △

2. 左側へ右側の画像をドラッグ
▲ ← △

3. 穴に埋め込まれているかどうかで認証する


パズル型CAPTCHAは非常に直観的で分かりやすい。
またこれはシステム試験の自動化をはかるエンジニアにとってもメリットがある。
各種技術の発達と競うように人間でも解読が難しくなってきている文字列を読み込み、
入力することは難しくとも、このパズル型CAPTCHAであればなんとか突破できるのではないだろうか。



 パズル型CAPTCHAを攻略するためのツール選定
WEBテストの自動化ツールとしては種々のものがある。
(例) Capybaraを使ってブラウザ操作を自動化

ただしこららはHTTPのプロトコルに従った操作がメインであり、純粋な画像の認識操作はできない。そこで今回はSikuliを使うことにした。
SikuliはOpenCVライブラリを使用しているので画像認識系の処理に強い。
パズル型CAPTCHAを突破するのにうってつけである。
画像を認識し、その画像をどう操作するかを指示できるため、利用範囲はブラウザ内アプリに限らない。デスクトップ上のアイコン操作なども可能になる。

今回はSikuliを利用してスクリプトを記述し、パズル型CAPTCHAを自動で処理させてみる。



仮想的なWEB環境とSikuliを使った試験の自動化概要
1. 
ブラウザのお気に入りに登録しているCAPTCHA埋め込み試験用サイトのアイコンをクリック。
試験用サイトのトップページへ画面が遷移する。
※HTTPプロトコルだけに限らない点を確認するためにGUI操作をやらせてみる。

2.
サイトにはパズル型CAPTCHAが埋め込まれている。
認証のために、パズルを実施。

3. 
画像を重ね合わせただけでは画面は遷移しない作りにしているので、
その下段にある、ログインアイコンをクリックする。



ツールの作成
Sikuliのインストール方法、設定方法は公式WEBを参照のこと。

Sikuliではjruby、jythonが利用できる。
ここではjrubyを使う。文法はruby(cruby)と同じである。

ただし、Sikuli用の関数も使うのため、
詳細な利用方法はSikuliのマニュアルを参照すること。


自動クリック用関数
試験項目の1.3.用の関数を作る(緑色はSikuli関数である)。
def translate(picture, similarity)
  if exists(Pattern(picture).similar(similarity), 0)
    click(picture)
    sleep (2) # wait for loading
  end
end

アイコンをクリックする場合。
translate("icon.png", 0.8)

ログイン画像をクリックする場合。
translate("login.png", 0.8)


マウスを乗せると画像の色が変わったりするサイトもあるだろう。
そのために、similarityを下げておいた。
画像の合致度を正確に合わせたい場合は、
検索対象の画像とクリック画像を分ければいいだろう。
find("picture_link.png")
click("picture_hover.png")

また合致度のディフォルトはグローバルに指示もできる。
そうすれば個別に指定する必要はなくなる。
Settings.MinSimilarity = 0.80


CAPCH操作用関数
いよいよ、試験項目2.の関数を作る。
パズル用に用意されている画像は一つではないはずである。
色や形の違い、またその回転に違いが存在するとして、
その組み合わせを試す必要がある。

▲ ←
▲ ← 
▼ ←

最初は移動用の画像()と、移動後の穴あき画像(▲、▼)をそれぞれ用意していた。
しかし、移動画像をもとに合致度を下げて穴あき画像を認識させれば
大概うまくいくことが分かったのここでは移動前画像しか手元にないことを想定する。
画像の収集にも腐心しなくていいので楽だろう。

移動前の画像だけを取得していた場合、移動後の画像と
二つがマッチするのではないか、
という疑問を持つかもしれないが画像の検索範囲を絞れば問題は起きない。

 ----     ----
| ▲  | ← | △ |
 ----     ----

すべてのパターンを試しても、画像がマッチしないかもしれない。
その場合に備えてCAPTCHAをreloadして画像を変えて再試行する処理も入れる。


def captcha()
  # 数値はサイトの構成、GUI環境に合わせて変えること
  src_x=907; src_y=424; src_w=63; src_h=138
  dst_x=724; dst_y=424; dst_w=182; dst_h=138

  count=3

  colors = ["blue", "green", "yellow"]
  figures = ["pentagon", "square", "triangle"]
  rotations = ["0", "1", "2"]

  # windows環境であってもパスの区切りは'/'でないと認識できなかった
  path = "C:/Documents and Settings/test/test.sikuli"
  Dir.chdir(path)

  count.times do

    colors.each do | color |
      figures.each do | figure |
          rotations.each do | rotation |

            src_pic = "src_#{color}_#{figure}_#{rotation}.png"
            next if File.exists?(src_pic) == false

            dst_pic = src_pic

            src_pic_obj = Region(src_x, src_y, src_w, src_h).exists(Pattern(src_pic).similar(0.80), 0)
            dst_pic_obj = Region(dst_x, dst_y, dst_w, dst_h).exists(Pattern(dst_pic).similar(0.60), 0)

            if src_pic_obj && dst_pic_obj
              dragDrop(src_pic_obj, dst_pic_obj)
              translate("login2.png", 0.8)
              return
            end

        end
      end
    end

    translate("reload.png", 0.8)

  end
end

captcha()


 Sikuliを利用する上での注意点
Sikuliの実行環境では画像用に変数を利用するとエラーが出た。

コード内での画像変数利用例。
src_pic = "src_#{color}_#{figure}#_{rotation}.png"

静的に画像の有無を確かめているのだろう。
今回のように動的に画像を変更する場合はコンソールにエラーが出るが問題ない。
[error] Image: could not be loaded from C:/Documents and Settings/test.sikuli/src_

ただし、完全に変数にすると、
src_pic = "#{color}_#{figure}_#{rotation}.png"
というエラーがでて、ファイルがオープンできない、実行できない状態となった。
[error] EditorPaneTransferHandler: importData: Problem pasting text null

このあたり、次のバージョンでは改善してほしいところである。



2012年6月30日土曜日

ブラウザ上で自動操作


定期的にWindowsOSで、ブラウザを立ち上げ、
WEBのアプリケーションを開き、Windowsのあるローカルファイルの
内容を貼り付けるという単純作業をひたすらやることになった。

当然この作業を自動化する。


【作業の流れ】
(1) ブラウザを立ち上げ、アプリケーションを開く
(2) 専用のユーザ名とパスワードを入力してログインする
(3) WEBで必要項目を入力する
① 日時
② とあるチェックボックスを選択
③ ファイルの内容を貼り付け



【自動化するスクリプトに何を選ぶか】
候補は3つ。
(1) JScript
(2) VBScript
(3) PowerShell

(1)と(2)をまとめてWSH(Windows Scripting Host)というらしい。

調べたところ、WSHからPowerShellへ徐々に移行してくるようなことが書いてある。
ならばPowerShellを使うべきだろうか。しかしコードを見る限り直感的に使いたいと思わない。
VBScriptも本能的に触りたくない。
技術的判断ではなく、感覚的な意見で嫌だ嫌だと言っているのは
Windowsというプラットフォームに意欲がそがれてしまうからなのか。

それはさておき、もう少しそれぞれのスクリプトを見ていると
JScriptはJavaScriptにコードが似ていることに気がついた。
JavaScriptの実装になっており、同じランタイムを利用している様子。
言語の設計思想は大きく異なるようだが、まあいいだろう。
少しだけやる気がわく。ということで、本能を頼りに、JScriptを利用することにする。



【コード】
ファイル名は○○.jsでよい。

//  開くファイルの形式
var TRISTATE_TRUE       = -1;  // Unicode
var TRISTATE_FALSE      =  0;  // ASCII
var TRISTATE_USEDEFAULT = -2;  // システムデフォルト

//  ファイルのオープンモード
var FORREADING      = 1;  // 読み取り専用
var FORWRITING      = 2;  // 書き込み専用
var FORAPPENDING    = 8;  // 追加書き込み

// 開くファイル
file="C:/file1.txt"  // ファイル

var fs = new ActiveXObject("Scripting.FileSystemObject");
var file = fs.OpenTextFile(file, FORREADING, true, TRISTATE_FALSE);
var ip = file.ReadAll();
file.Close();

//  オブジェクトを解放
fs = null;

dd     = new Date();
year   = dd.getYear();
month  = dd.getMonth() + 1;
day    = dd.getDate();
hour   = dd.getHours();
minute = dd.getMinutes();

time = year + '/' +  month + '/' + day + '/' +  hour + '/' + minute;

// 変数内を確認したければ
// WScript.Echo(time);
// WScript.Echo(file);

use_ie();

function use_ie()
{
  // IEを起動
  var ie = WScript.CreateObject("InternetExplorer.Application")

  // 以下のリンクにアクセス
  ie.Navigate("http://example.com/hoge1.html");

  // アプリケーションを表示
  ie.Visible = true;

  wait(ie);

  // ユーザ名とパスワードを入力
  if (ie.document.getElementsByName("UserName").length !=0 ) {
    ie.document.getElementsByName("UserName")(0).value = "GS07604" ;
    ie.document.getElementsByName("Password")(0).value = "terasakiy68";
    ie.document.getElementsByName("_DominoForm")(0).submit();
    wait(ie);
  };

  ie.Navigate("http://example.com/hoge2.html");
  wait(ie);

  // <a href=~>の上から3番目のリンクへ飛ぶ(当然アプリ依存)
  ie.Navigate(ie.document.getElementsByTagName("a")(3).href);
  wait(ie);

  ie.document.getElementsByName("Restriction")(1).checked = true;
  ie.document.getElementsByName("Reason")(0).checked = true;
  ie.document.getElementsByName("SMTP_Log")(0).value = log;
  ie.document.getElementsByName("RestrictionTo")(0).value = ip;
  ie.document.getElementsByName("StartTime")(0).value = time;
}


// IEがビジー状態の間待つ
function wait( ie )
{
  while( ( ie.Busy ) || ( ie.readystate != 4 ) )
  {
    WScript.Sleep( 100 );
  }

  WScript.Sleep( 1000 )
}