PuppeteerでUIテストを試してみた

## はじめに
`Puppeteer`というAPIを利用して画面操作のテストを行うサンプルを作成したことを備忘として残します。
今回はローカルサーバーの掛け算を行う画面にアクセスし、値の入力と計算結果の検証までを自動化するテストコードを作成しました。
（本来のやりたかったことはSPAの画面操作を自動化することでしたが、簡単に動きを確認するために作成しました）

## Puppeteerとは
`Googleで開発されて、DevToolsプロトコルを介してChromiumやChromeを制御するための高レベルなAPIを提供するNodeライブラリ`
とのこと。
`Google Chrome`のヘッドレスブラウザ（GUIを必要としないブラウザ）機能であるヘッドレスChromeを操作するものがこの`Puppeteer`になります。
GitHubのスター数は8万以上, 最新バージョンは13日前（本記事執筆時点）と今後も盛り上がりを見せそうなライブラリです。
`Puppeteer`が登場する以前は`PhantomJS`がヘッドレスブラウザの主流だったようです。

https://github.com/puppeteer/puppeteer

## 実行環境

|  | バージョン |
|:-:|:-:|
| node | v18.12.1 |
| npm | v8.19.2  |
| puppeteer | v19.2.2 |
| http-server | v14.1.1 |

## 手順
### 最終的なディレクトリ構成

```bash
puppeteer-sample
├── node_modules
|       ︙
|
├── package-lock.json
├── package.json
└── test.js
```

### 1. セットアップ

```bash
mkdir puppeteer-sample
cd puppeteer
npm init
npm install puppeteer
︙
npm install http-server
︙
```

### ２. テスト対象のページ作成

<details><summary>テスト対象ページ</summary>

```index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Puppeteer UI Test</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
</head>
<body>
<section class="section">
    <div class="container">
        <h1 class="title">
            Puppeteer Test Form
        </h1>
        <div class="field">
            <label class="label">かける数</label>
            <div class="control">
                <input id="first" class="input" type="number">
            </div>
        </div>
        <div class="field">
            <label class="label">かけられる数</label>
            <div class="control">
                <input id="second" class="input" type="number">
            </div>
        </div>
        <div class="field is-grouped">
            <div class="control">
                <button class="button is-link" onclick="multiply()">計算する</button>
            </div>
        </div>
        <div class="field">
            <label class="label" id="out">掛け算の答え:</label>
        </div>
    </div>
</section>
<script>
    function multiply() {
        const first = document.querySelector('#first');
        const second = document.querySelector('#second');
        const out = document.querySelector('#out');

        const first_num = parseFloat(first.value);
        const second_num = parseFloat(second.value);

        const result = first_num * second_num;
        out.innerHTML = result;
    }
</script>
</body>
</html>
```
</details>

#### テスト対象の画面
かける数, かけられる数の入力後、「計算する」ボタンをクリックしてJS関数を実行。
画面に挿入された結果の検証を行います。
![スクリーンショット 2022-11-16 18.05.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2663462/efe2e762-e70e-fe31-d2c1-462fcc538c8a.png)


### 3. テストコードを作成

<details><summary>テストコード</summary>

```test.js
const assert = require('assert')
const puppeteer = require('puppeteer')

if (require.main === module) {
    main()
}

async function main () {
    try {

        const browser = await puppeteer.launch({
            headless: false, // falseにするとGUI表示して動作確認できるようになる
        })

        try {
            const page = await browser.newPage();       // 新規タブを開く

            await Promise.all([
                page.waitForNavigation({timeout: 1000}),
                page.goto('http://localhost:8080/'),    // localhost:8080にアクセス
            ]);

            await page.type('#first', '100');           // 値を入力する
            await page.type('#second', '50');
            await page.click('button.button.is-link');  // 「計算する」ボタンをクリックする

            // 要素内テキストを取得
            const actual = await page.$eval('#out', el => el.innerHTML);
            const expected = '5000';                    // 期待値

            console.info({actual, expected});
            assert.strictEqual(actual, expected);       // 乗算結果の検証
            await page.screenshot({path: 'example.png'}); // ページのスクリーンショットを撮る

            await new Promise(resolve => setTimeout(resolve, 1000));
        } finally {
            await browser.close() // タブを閉じる
        }
    } catch (err) {
        console.error(err)
    }
}
```
</details>

### ４． テスト実施
ローカルサーバーを立ち上げる
```bash
npx http-server
```

テスト実行
```bash
node test
```

テスト合格の場合は以下のように
```bash
{ actual: '5000', expected: '5000' }
```

不合格の場合は以下のように出力されます（期待値を変更してテスト実行）
```bash
{ actual: '5000', expected: '5001' }
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

'5000' !== '5001'

    at main (/Users/xxx/puppeteer-sample/test.js:31:20)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: '5000',
  expected: '5001',
  operator: 'strictEqual'
}
```

## 参考
- [公式ドキュメント](https://pptr.dev/)
- [第1章 Puppeteerの魅力は何か ～ヘッドレスChromeを自由自在に操る](https://gihyo.jp/article/2022/09/rapid-learning-puppeteer-01#gh9VjaNReZ)