課題:DOMを直接書き換えると反映されない
DOMでValueを書き換えても、反映されないJSのフレームワークが幾つかある。Angularとか。あのあたりをつかったログインフォームをかんたんに入力したい。
実行例
<input name="sample" size="18" id="sample"> <input id="sample_btn" type="button" value="イベント発火" onclick=" let inputElement = document.querySelector('input[name=sample]') var event = new Event('input', { 'bubbles': true, 'cancelable': true }); inputElement.value = '070-1234-5678' inputElement.dispatchEvent(event); ">
方法1 html のイベントをトリガーして文字列を突っ込む
最近は、domを直接書き換えないでJSでオブジェクトをObserveしていることが多いので、イベントを発火させて文字列を入力する。
inputElement = document.querySelector('input[name=nationalId]') var event = new Event('input', { 'bubbles': true, 'cancelable': true }); inputElement.value = '12345678' inputElement.dispatchEvent(event);
めっちゃ単純ですね。通常通りDOMを書き換えたあとに、dispatchEvent
で new Event('input')
を発行してやるだけです。
これで、擬似的にフォームの入力をprogrammatically に試行できます。
2025-04-14 追記。
puppeteer などで使おうとすると、page.evaluate()に仕込むのだが。若干の遅延を仕込んでおかないと、早すぎて next.js や angular のイベント処理より早く関数を抜けてしまう。イベントバブルが終了するのを待つのはちょっとしんどいのでウェイトを入れておくと安定する。
let ret = await page.evaluate( async(sel,val )=>{ let input_value = async ( s, v )=>{ let inputElement = document.querySelector(s) inputElement.focus(); await new Promise(resolve => setTimeout(resolve, 50));// 極僅かな遅延。 let evInput = new Event('input', {'bubbles': true,'cancelable': true}); let evChange = new Event('change', {'bubbles': true,'cancelable': true}); inputElement.value = v; inputElement.dispatchEvent(evInput); inputElement.dispatchEvent(evChange); } await input_value( sel,val ) await new Promise(resolve => setTimeout(resolve, 100)); //若干の遅延 },'input[type=password]','Strong@@@PassWord123456' )
page.type や page.focus でもいいと思いがちだが、スクレイピングするなら、evaluate がオススメだ。入力はevaluate 内部に閉じ込めておくほうが、試行錯誤の効率が飛躍的に良い。なぜならevalute()はブラウザの開発ツールでコンソールへコピペと同じだからだ。コピペして動いたものをpupeteer側へコピペすればいいので、とても楽なのだ。
方法2 execCommand
いにしえの手法 focus/ execCommand を使うともっと楽ちん。
document.querySelector('#tel001').focus() document.execCommand('insertText', false, "090123456");
こちらは、focus
したうえで、 execCommand
を発行する。
focus
したうえでイベントを発行するなら、他イベントも行けそうです。ただイベントを調べたりサポート状況が曖昧なので、execCommandという枯れた物を使います。
昔のやつ
https://takuya-1st.hatenablog.jp/entry/2014/11/12/162008
AngularJSのやつ
https://takuya-1st.hatenablog.jp/entry/2021/12/15/015820
Angular/AngularJSの場合は、スコープを取り出せば入力ができますが、フレームワークに特化すると後々が面倒そうなのでイベントを発火するほうが無難だと思う。
追記:パスワード自動入力ソフトの場合。
パスワード入力をするChrome のExtensionではどういう実装になっているのかというと、Bitwardenのソースコードを覗いてみました。
しつこくイベントを発行してました。
var event1 = el.ownerDocument.createEvent('HTMLEvents'), event2 = el.ownerDocument.createEvent('HTMLEvents'); el.dispatchEvent(doEventOnElement(el, 'keydown')); el.dispatchEvent(doEventOnElement(el, 'keypress')); el.dispatchEvent(doEventOnElement(el, 'keyup')); event2.initEvent('input', true, true); el.dispatchEvent(event2); event1.initEvent('change', true, true); el.dispatchEvent(event1);
https://github.com/bitwarden/browser/blob/master/src/content/autofill.js
ちなみに、bitwarden がこれだけイベント発火しても、一部のサイト(TP-Linkのルータログインページなど)では入力がうまくJS側に拾われないんですよねぇ。