Puppeteerのセットアップから使い方まで〜ブラウザ操作の自動化〜

## はじめに
Puppeteerを使ってみたら、とても簡単にブラウザ操作が自動化できたことに感動しました。
セットアップの方法とよく使うPuppeteerのAPIについてまとめましたので、これから使ってみようかなと思っている方の参考になれば嬉しいです。

## Puppeteerとは
[Puppeteer](https://github.com/GoogleChrome/puppeteer)

読み方は「ぱぺてぃあ」。日本語だと人形使いという意味です。
Chromeブラウザを操作できるNodeのライブラリで、Chrome DevToolsのチームが開発を行っています。

## Puppetterの特徴
ブラウザ操作ができるツールとしては、Selenium Webdriverが有名です。
Selenium Webdriverとの違いは、Puppeteerはヘッドレスブラウザを使うことができるので、高速に動作させることができます。
また、PuppeteerはChromeのブラウザしか操作ができません。

## 使ってみる
### セットアップ
Node.jsが動く環境が必要です。

```
$ npm i puppeteer
```

サクッと試したいだけならPuppeteerがWebツールを提供してくれているので、そちらを使うと便利です。

[Try Puppeteer](https://try-puppeteer.appspot.com/)

### テストを書く
簡単なテストシナリオを書いてみます。
下記はGoogleのトップページに遷移して、画面のスクリーンショットを撮るテストシナリオです。


```javascript:test.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.google.com');
  await page.screenshot({path: 'screenshot.png'});

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

ベースはこんな感じです。
`Browser`のインスタンスを作成した後にPuppeteerのAPIを使って、ページ内のリンクをクリックして別ページに遷移したり、フォームを操作したり、ブラウザを操作していきます。

### テストを実行する
下記コマンドを実行します。

```
$ node test.js
```

## よく使うAPI
### 指定したURLにアクセスする
```javascript
page.goto('https://www.google.com/');
```

### クリック
```javascript
page.click('input[type="submit"]');
```
クリックする要素の指定には、CSSセレクタを使用します。指定したセレクタが存在しない場合は、エラーになります。

```terminal
$ node test.js
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement at ElementHandle._clickablePoint
```

### フォーム操作
```html
<form method="post" action="/update">
  <label for="name">名前</label>
  <input type="text" name="name" id="name">
  <label for="email">メールアドレス</label>
  <input type="text" name="email" id="email">
  <label for="gender">性別</label>
  <select id="gender" name="gender">
    <option value="m">男性</option>
    <option value="f">女性</option>
  </select>
  <label for="inquire">問い合わせの内容</label>
  <input type="radio" name="inquire" value="1" id="inquire_1"> 商品に関する問い合わせ
  <input type="radio" name="inquire" value="2" id="inquire_2"> その他
  <input type="submit" value="送信">
</form>
```
例えば上記のフォームを操作するAPIは以下のとおりです。

```javascript
// テキストフィールドに値を設定する
page.type('input[name="name"]', 'kanoe');
page.type('input[name="email"]', 'kanoe@xxxx.xxx');
// プルダウンから選択する
page.select('#gender', 'f');
// ラジオボタンを選択する
page.click('#inquire_1');
```

### デバイス切り替え
Puppeteerには既にデバイスのリストを定義しているので、そちらを使って切り替えると便利です。
定義されているデバイスは下記コードから確認できます。
https://github.com/GoogleChrome/puppeteer/blob/master/DeviceDescriptors.js

```javascript
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
// iPhone6を指定してみる
const iPhone = devices['iPhone 6'];

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto('https://www.ozmall.co.jp/');
  await page.screenshot({path: 'example.png'});
  await browser.close();
})();
```

デバイスを指定する方法以外にも、`page.setUserAgent`を使うことでUserAgentを直接指定することも可能です。

```javascript
page.setUserAgent('Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');
```

### 待機
用途としては、JavaScriptのeventが起きるまで待機するために使うことが多いAPIです。

```javascript
// imgタグが見つかるまで待機、見つかったらconsoleにログを出力する
page.waitFor('img')
    .then(() => console.log('gazou attayo!'));
// 1s待機
page.waitFor(1000)
```

### 画面のキャプチャ
```javascript
// fullPageのオプションを指定すると、フルページでスクリーンショットが撮れる
page.screenshot({path: 'example.png', fullPage: true});
```

### 特定の要素に対して操作する
特定の１要素だけ取得して操作する場合は、`page.$eval`を使います。

```javascript
// itemクラスを持つ最初の要素のテキストを取得する
const tab = await page.$eval('.item', el => el.textContent);
```

ページ内のセレクタを全て取得したい場合は、`page.$$eval`を使う必要があります。

```javascript
// ページ内の複数のセレクタの内、特定の要素だけをクリックする
page.$$eval('#nav > .item', tabs => {
  tabs.filter(tab => tab.textContent === 'Contact')[0].click();
});
```

## 感想
UIテストを書くとき、Selenium Webdriver + 何かしらのテストフレームワークでテストをかくことが多いと思います。
わたしも、以前Selenium WebdriverとCodeceptionを使ってE2Eテストを構築したことがあるのですが、ブラウザのドライバの仕様に引っ張られて苦労することが多々ありました。
PuppeteerはChrome開発チームが開発していることもあって、ストレスなくブラウザ操作が実現できるのがとてもよかったです。あと環境構築もとても簡単なので、ちょっと試したい時にすぐ使えるのは便利だなーと思いました。