inputタグに設定できるtype属性は今のところ、以下の20種類
button, checkbox, color, date, datetime-local, email, file, hidden, image, month, number, password, radio, search, submit, tel, text, time, url, week
puppeteerで簡単に入力できるもの、専用の入力方法が用意されているの、クセが強いもの、など様々だったのでまとめた。
なお、本ページの例は全タグにidがふってある優しい世界なので、要素の特定は別途頑張ってください。
確認環境
- Windows10 2004
- node v14.15.4
- puppeteer 5.5.0
- Chromium 90.0.4400.0
text系
以下のtypeを、まとめてtext系と呼ぶことにする。
email, number, password, search, tel, text, url
htmlだとこんなかんじ
<inputtype="text"id="text1"><inputtype="email"id="email1"><inputtype="search"id="search1"><inputtype="tel"id="tel1"><inputtype="url"id="url1"><inputtype="number"id="number1"><inputtype="password"id="password1">
submit時にvalidationがかかるもの、入力した文字が見えないものなど、それぞれ違いはあるが、入力する上では全てtype()
を利用すれば良い
awaitpage.type("#text1","text");awaitpage.type("#email1","example@example.com");awaitpage.type("#search1","search text");awaitpage.type("#tel1","09012345678");awaitpage.type("#url1","https://example.com/");awaitpage.type("#number1","42");awaitpage.type("#password1","yourpassword");
button系
以下のtypeを、まとめてbutton系と呼ぶことにする。
button, image, reset, submit
htmlだと以下
<inputtype="reset"id="reset1"><inputtype="button"id="button1"value="ボタン"><inputtype="submit"id="submit1"><inputtype="image"id="image1"src="image.png"alt="image-alt-text">
それぞれ役割があるが、入力(クリック)はどれもclick()
でOK
awaitpage.click("#button1");awaitpage.click("#image1");awaitpage.click("#reset1");awaitpage.click("#submit1");
radio, checkbox
ラジオボタンやチェックボックスも、素直にclick()
でOK
<inputtype="radio"id="radio1"><inputtype="checkbox"id="checkbox1">
awaitpage.click("#radio1");awaitpage.click("#checkbox1");
file
<inputtype="file"id="file1">
type="file"には、専用のメソッドであるwaitForFileChooser()
が用意されている。
使い方は、waitForNavigation()
と同様に、クリック前に実行しておき、resolve後、ファイルを指定する。headlessでなくてもファイル選択のUIは表示されない。
const[fileChooser]=awaitPromise.all([page.waitForFileChooser(),page.click('#file1'),]);awaitfileChooser.accept(["/path/to/file"]);
もしくは、以下でも可。個人的にはネストが深くならないこちらのほうが好み。読みやすさは大差ない。
constfileChooserPromise=page.waitForFileChooser();awaitpage.click('#file1');await(awaitfileChooserPromise).accept(["/path/to/file"]);
range
スライドバーが表示されるtype="range"
<inputtype="range"id="range1">
単純な値指定は難しいため、focus()
でフォーカスを合わせて1、右矢印キー
か左矢印キー
を適切な回数押すと良い。(1回でstep属性の値だけ変化、デフォルト1)
await(awaitpage.$("#range1")).focus();for(leti=0;i<20;i++){awaitpage.keyboard.press("ArrowRight");}
color
<inputtype="color"id="color1">
puppeteerの闇を感じることができるtype="color"。
クリックするとカラーピッカーが表示されるが、puppeteerに専用のメソッドは用意されていない2ため、このカラーピッカーを操作する必要がある。
やってみた結果が以下。
await(awaitpage.$("#color1")).click();awaitpage.waitForTimeout(300)awaitpage.keyboard.press("Tab")awaitpage.keyboard.press("Tab")awaitpage.keyboard.press("Tab")awaitpage.keyboard.type("255")awaitpage.keyboard.press("Tab")awaitpage.keyboard.type("0")awaitpage.keyboard.press("Tab")awaitpage.keyboard.type("0")awaitpage.keyboard.press("Enter")
まずクリックでカラーピッカーを表示させ、タブを3回押すことでRGB値の入力欄に移動。
タブで移動しながらR,G,Bそれぞれの値を入れることで入力している。
クリック後300ms待っているのは、カラーピッカーが表示されるまでに若干時間がかかるため。ここは環境によって変える必要があるだろう。
DOM要素ならwaitForSelector()
などで待つことができるが、カラーピッカーはDOM要素でない(・・・よね?)ため仕方なくwaitForTimeout()
を利用している。
もうお気づきだろうと思うが、完全にChromiumのカラーピッカーのUIに依存した書き方になっているため、Firefoxではおそらく動かない(未検証)し、今後Chromiumのバージョンアップで動作しなくなる可能性もある。闇だ。
なお、puppeteerでは、ページ上でJavaScriptを動作させることもできるため、以下の書き方もできる。これなら1行だ。
await(awaitpage.$("#color1")).evaluate((node)=>{node.value="#FF0000"});
ただ、この書き方だと、changeイベントが発火しないため、テスト内容によっては利用できない。
date系
以下のtypeを、まとめてdate系と呼ぶことにする。
date, datetime-local, month, time, week
htmlでは以下。
<inputtype="date"id="date1"><inputtype="datetime-local"id="datetime-local1"><inputtype="month"id="month1"><inputtype="time"id="time1"><inputtype="week"id="week1">
まず、入力ボックスの右側のカレンダーや時計をクリックするとピッカーが表示されるが、これを使おうとしてはいけない。マウスでしか開けない(多分)し、ピッカーはDOM要素でないため、開いた後の操作が難しい。ブラウザごとの差異も激しい。
キーボードで日付/時刻を入力するのが最善だ。
その上で、以下の点に気をつける必要がある。3
- 項目が一意に特定可能になると、次の項目に自動で遷移する
- 「月」に5を入力すると自動で「日」にフォーカスが移るが、1を入力してもフォーカスはそのまま(10-12月があるため)
- 最大275760年9月13日まで入力できる4
- つまり「年」に4桁入力しても、「月」にフォーカスを移してくれない
- min,max属性の指定によっては、入力不可な項目が出てくる
- type="date"で、min="2020-01-01" max="2020-12-31"の場合、「年」は2020に固定され、フォーカスが当たるといきなり「月」の入力になる。
- ロケールによって年月日の順番が異なる
1.の解決策は2つ。
1つ目は、タブや右矢印などでフォーカスを移すこと。
「月」に1と入力したあとでタブを押せば、フォーカスが「日」に移ってくれる。
ただし、puppeteer上では月をtype()
したあと、月が1のときのみタブをpress()
してまた日をtype()
する、という少し面倒な書き方になる。5
おすすめは次の2つ目だ。「月」を0埋めし、必ず2桁入力する。puppeteerのコードとしては、タブを押すよりだいぶ簡単になる。
2.はほとんど1.と同種の問題だ。
「年」を入力したあと、タブでフォーカスを移してもよいが「年」の頭に00を加えて6桁入力するのが楽だ。type()
のみですむ。
また、自動化対象のコードを変更できる場合、maxを指定してしまっても良い。max="9999-12-31"としておく6と、4桁入力した時点で「月」にフォーカスが移ってくれる。あと7900年後くらいまでは困る人もいないだろう。
3.は少し厄介だ。
「年」が入力不可だと分かっているのなら単純に月と日だけ入力すればよいだが、例えば「現在の日付から半年後まで」という仕様の場合、1-6月と7-12月で年の入力可否が変わってしまう。「1年後の前日の日付まで」という仕様なら1月1日のみCIが落ちるかもしれない。7
解決策としては、minとmax属性を読み込んで場合分け、が愚直な解決方法だろうか。
尤も、自動テストという文脈なら、テストの外部要因(時刻)でテスト内容や結果が変わるテストはイマイチなので、現在時刻をDIできる設計にするのが望ましい。8
4.は解決策があるか調査中である。現在、年月日がどの順番で表示されているのか、を取得する方法があれば知りたい。
ということを踏まえて、入力するコードは以下となる。(3と4は検討外)
awaitpage.type("#date1","0020210127");awaitpage.type("#datetime-local1","00202101270812");awaitpage.type("#month1","00202012");awaitpage.type("#time1","0212");awaitpage.type("#week1","00201943");
3や4が問題になる場合、changeイベントが発火しない、などの差分が許容できるなら、以下の書き方も利用できる。
await(awaitpage.$("#date1")).evaluate((node)=>{node.value="2021-01-27"});await(awaitpage.$("#datetime-local1")).evaluate((node)=>{node.value="2021-01-27T08:12"});await(awaitpage.$("#month1")).evaluate((node)=>{node.value="2020-12"});await(awaitpage.$("#time1")).evaluate((node)=>{node.value="02:12"});await(awaitpage.$("#week1")).evaluate((node)=>{node.value="2019-W43"});
hidden
hiddenは普通はpuppeteerから基本的に書き換えないし、書き換えれない。
どうしてもやりたければ、evaluate()
でやるしかない。ブラウザ自動操作の枠を超えているような気もするが。
<inputtype="hidden"id="hidden1">
await(awaitpage.$("#hidden1")).evaluate((node)=>{node.value="hidden-value"});