テレビ番組への観覧応募をpuppeteerで自動化する

大好きなテレビ番組の観覧募集にいつも応募してるのですが、なかなか当選しません:sob:
毎回名前やら住所やらを入力するのが面倒になってきたので、puppeteerで観覧応募を自動化してみました。

※あんまり良くないかなと思って具体的な番組名やサイトのURLを載せるのはやめました。なので単にフォーム入力の自動化の方法を紹介する内容になってます:sweat_smile:

## puppeteerとは
ヘッドレスChromeを操作するためのNode.js APIを提供するライブラリです。
puppeteerを使うと、Webサイトを開いたり、WebサイトのページのスクリーンショットやPDFを取得したり、入力欄に文字を入力したりと、私たちがブラウザ上で手動で行う操作のほとんどをプログラムから実行することが可能になります。
puppeteerはWebアプリケーションのE2Eテストで使用されるケースが多いと思います。

以前にpuppeteerのサンプルプログラムを紹介する記事を書いたのでよかったらこちらもご覧ください。
[ヘッドレスChromeライブラリpuppeteerの全てのサンプルプログラムを試してみた](https://qiita.com/takehilo/items/93a2fb34a815e2819257)

## 画面構成
番組の観覧募集のページに行くと、以下のようなフォーム画面が表示されます。
このフォームはiframeで埋め込まれています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/81715/8ae075f5-9ee7-f7ba-32c0-9139462e5ce1.png)


## スクリプトの解説
```javascript
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 1
  await page.goto('サイトURL');
  const frame = page.frames()[1];
  await frame.waitForSelector('input[name="quest[1]"]');

  // 2
  await frame.type('input[name="quest[1]"]', 'あなたの氏名');
  await frame.type('input[name="quest[2]"]', 'あなたのふりがな');
  await frame.type('input[name="quest[3]"]', 'あなたの年齢');
  await frame.click('input[name="quest[4]"][value="1"]');
  await frame.select('select[name="quest[5]"]', 'あなたの都道府県');
  await frame.type('input[name="quest[6]"]', 'あなたの電話番号');
  await frame.type('input[name="quest[7]"]', 'あなたのメールアドレス');
  await frame.type('textarea[name="quest[8]"]', 'あなたの同伴者の氏名・性別・年齢');

  // 3
  await frame.click('input[name="confirm"]');
  await frame.waitForSelector('input[name="commit"]');

  // 4
  await frame.evaluate(() => {
    const name = document.querySelector('.formDiv');
    name.scrollIntoView();
  });
  await page.screenshot({path: 'confirm.png', fullPage: true});

  // 5
  await frame.click('input[name="commit"]');

  // 6
  await page.screenshot({path: 'finish.png', fullPage: true});

  await browser.close();
})();
```

スクリプトの内容は以下のとおりです。

1. 観覧募集ページを開く
2. 氏名、ふりがな...を入力する
3. 「確認」ボタンをクリックして確認画面に遷移する
4. 「確認」画面のスクリーンショットを取得する。その際、確認フォームの内容が全部表示されるようにスクロールさせておく。 
5. 「送信」ボタンをクリックして完了画面に遷移する
6. 「完了」画面のスクリーンショットを取得する

### 1. 観覧募集ページを開く
観覧募集の入力フォームはiframeで埋め込まれており、puppeteerでは`page.frames()[1]`で2番目のフレームとして取得できます。
以降はこのフレームに対して操作をすることになります。

観覧募集ページを開いたら、`frame.waitForSelector(selector)`でフォームが表示されるまで待ちます。

### 2. 氏名、ふりがな...を入力する
`frame.type(selector, text)`でテキスト入力欄に入力をします。
ラジオボタンは`frame.click(selector)`で選択します。
セレクトボックスの操作には専用の`frame.select(selector, ...values)`というAPIが用意されています。
`values`にはoption要素で指定するvalue属性の値を指定します。例えばここでは`11`を指定すると、埼玉県を選択したことになります。

### 4. 「確認」画面のスクリーンショットを取得する
確認画面は以下に示すようにスクロールさせないと全ての項目が表示されません。
このままスクリーンショットを取ってしまうと「同伴者の氏名・性別・年齢」の部分が切れてしまいます。

![confirm.gif](https://qiita-image-store.s3.amazonaws.com/0/81715/18f7eb85-d34b-8c3b-5d32-c1851a2314a0.gif)

そこで、「氏名」欄が一番上に来るところまでスクロールさせてからスクリーンショットを取得するようにしています。
以下のコードがその部分です。
`frame.evaluate(pageFunction)`は`pageFunction`で渡した関数をブラウザのコンテキストで実行します。
Node.jsのコンテキストでは`document.querySelector`はありませんが、この関数はブラウザ上で実行されるので問題なく動作します。

```javascript
await frame.evaluate(() => {
  const name = document.querySelector('.formDiv');
  name.scrollIntoView();
});
```

確認画面のスクリーンショットは以下のようになります。
※実際に取得される画像は全画面ですが、ここではフォーム部分だけ切り取っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/81715/75a9bf29-6650-bd42-0f97-5101c26db34b.png)

## まとめ
puppeteerはE2Eテストで使うものとして認知されていると思いますが、今回のようにちょっとしたライフハックにも使用できる便利なライブラリです。
是非みなさんもpuppeteerを使ってみてください。