RPAの仕組みを知りたくてコツコツと研究し、ついにPowerShell使ってWebDriver経由でブラウザ側のクリックされた要素を取得できる様になった話。
過去には、ブラウザ操作に関する仕様を読み漁り。
- PowerShellでSelenium使わずWebDriver操作
- W3C WebDriver APIをPowerShell制御まとめ①
- W3C WebDriver APIをPowerShell制御まとめ②
- W3C WebDriver APIをPowerShell制御まとめ③
しかし、結局、ブラウザを操作する方法はわかってもブラウザが操作された内容の取得の仕方はわからず。
それから、Selenium IDEのソースコード読んだり(ほぼ理解できなかったけど)して
いよいよ、それらしくなって来たのでございます!
1.結論から見せようじゃないか。
PowerShellを開いて、カレントフォルダにWebDriverを置いて以下のコマンドを張り付け。
DOS窓上がって、ブラウザ起動して、スクリプト実行ライブラリのページが開く。
# WebDriverの起動
Start-Process -FilePath chromedriver.exe -ArgumentList port=9515
# DOSプロンプトが起動する。
# Uriと連想配列を渡してJsonでPostするfunctionを定義
function Post-JsonContent($postUri,$body)
{
$json = $body | ConvertTo-Json -Compress
$postBody = [Text.Encoding]::UTF8.GetBytes($json)
return Invoke-RestMethod -Method POST -Uri $postUri -Body $postBody -ContentType application/json
}
# 空のブラウザを起動
$body = @{desiredCapabilities=@{}}
$uri = "http://localhost:9515/session"
$rsp = Post-JsonContent $uri $body
# セッションIDを保持
$sessionId = $rsp.sessionId
$uri = $uri + '/' + $sessionId
# URLでページ遷移
$body = @{url="https://yizm.work/winactor_script_lib/"}
$url_uri = $uri + '/url'
$rsp = Post-JsonContent $url_uri $body
以下のコードを張り付け。(プロンプトは戻らない。)
そして、ブラウザ上で「p,a,div,span,input,textarea」いづれかの要素をクリックする。
# クリックイベントを監視、クリックされた要素の情報を返す。
$body = @{script="
var callback = arguments[0];
var triggerElms = document.querySelectorAll('p,a,div,span,input,textarea');
var getElm = function(e){
let node = e.target;
for(i=0 ; i<triggerElms.length ; i++){ triggerElms[i].removeEventListener('click',getElm); }
callback({id:node.id, name:node.name, class:node.className, value:node.value, text:node.innerTEXT, html:node.outerHTML});
}
for(i=0 ; i<triggerElms.length ; i++){ triggerElms[i].addEventListener('click',getElm,true);} ";args=@()}
$elm_uri = $uri + '/execute/async'
$rsp = Post-JsonContent $elm_uri $body
プロンプトが戻ってきたら確認。
$rsp.value
キャプチャ動画撮って公開したかったけど、フリーのキャプチャ取得ツールがうまくうごかなかったので、そこは今度取ることにする・・・。
2.わかってしまえば理屈は簡単だった。
非同期のスクリプト実行(Execute Async Script)
リクエスト
(POST) http://localhost:9515/session/{sessionId}/execute/async
これを使う訳なんだが、bodyはsyncのスクリプト実行と一緒で、scriptとargsを使う。
@{script="//*ここにJavascript*//";args=@("ここに引数")}
で、scriptで渡されたJavascriptの中では、argumentsにargsで渡した引数が受け取られる訳なんだけども、asyncコマンドの場合は、args引数とプラワンで一番最後の要素にコールバック用の関数が渡されることになっている様だ。
だから、このシンプルな例だと引数なしで実行したarguments[0]にコールバック関数がいることになる。
ほんだらあとは、EventListener仕込んで取りたい要素がクリックされたタイミングでコールバック関数を呼び出すだけで良い訳だ。
コールバック関数の引数に渡したデータはjsonでプロンプトに返る。
非同期って言葉に惑わされてPromiseだのresolveだの勉強してたけど、コールバック関数の受け取り方だけわかったら特に小難しいこと考える必要なかった・・・。
こんな処理をループで回せば、連続した操作をポコポコ取得し続けることもできるね。
hoverとかのイベントで枠のハイライトとかしたりすれば、もっとそれらしくなるんだろうけど、そーゆーカッコイイのはすでに世に溢れてるしね?
仕組み分かったから、今日のところはこのくらいでいいか?
