Puppeteerの上位互換？自動テストにPlaywrightを使ってみる

# はじめに
みなさんはE2Eテストを行う時にはライブラリに何を使用していますでしょうか？
僕は普段Google謹製のPuppeteerを利用しているのですが、ちょっと前に話題になってから常に動向を気にしていたPlaywrightが最近バージョン1.0.0のメジャーリリースが行われたということもあって、「これは実際に触ってみたい！」と思い触ってみましたので、備忘録としてここに記そうと思います。

# Playwrightとは
PlaywrightとはMicrosoftが提供しているChrome、Safari、Firefoxに対して、CLI上でヘッドレスブラウザを操作できるNode.jsのライブラリです。
かなり簡単な記述で対象ページに対してテストを実施することができます。
後述するコードを見てもらえるとすぐわかるかもしれませんがPuppeteerを知っている人はほぼ同じなので読みやすいかもしれません。
もともとPuppeteerを開発していたGoogleの開発チームがMicrosoftに移動して作成したライブラリのようなので、非常に似ているのかもしれないです。

公式github: [Playwright](https://github.com/microsoft/playwright)

公式ページ: https://playwright.dev/

# Puppeteerとの違い
書きかたは先ほど記載した通り、ほとんどPuppeteerもPlaywrightも変わらないです。違う点としては実行可能なブラウザがPuppeteerと比べて多いのが特徴です。
最近Puppeteerのv2.1がリリースされたタイミングでChromeだけでなくFirefoxも実行できるようになりましたが、Playwrightはそれに加えてwebkitでの実行が可能なライブラリなので、対象ブラウザだけ見るとぱっと見上位互換感があります。

# Installation
インストールするにはnpmが利用できる環境下で以下のコマンドを実行するだけです。

```
$ npm i playwright
```

# 使い方

## 簡単なテストコード
簡単なテストコードの例としてChrome、Firefox、Safariで使用しているブラウザがわかるサイトのスクリーンショットを撮るテストコードを記載します。
今回は[こちらのページ](https://yourbrowser.is/)にアクセスして現在使用しているブラウザを確認してみようと思います。

```js:example.js
const playwright = require('playwright');

(async () => {
  for (const browserType of ['chromium', 'firefox', 'webkit']) {
    // ここのbrowserTypeを変えることで、対象のブラウザを変更することができます。
    const browser = await playwright[browserType].launch();
    const context = await browser.newContext();
    const page = await context.newPage();

    await page.goto('https://yourbrowser.is/');
    await page.screenshot({ path: `example-${browserType}.png` });
    await browser.close();
  }
})();
```

```js
const browser = await playwright[browserType].launch();
```
の部分で立ち上げるブラウザを決定しています。

```js
const context = await browser.newContext();
```
上記newContextで引数に設定を渡すことでUA偽装などを行うことができます。
今回は特にないので、引数なしで実行しています。

```js
const page = await context.newPage();
```
上記部分で立ち上げたブラウザでタブを作成しているイメージです。
基本的にここで立ち上げたpageに対してページ遷移をさせたりなどの動作を実行していきます。


## テスト実行方法
通常通り、```$ node $(ファイル名)```で実行可能です。

```
$ node example.js
```
上記コマンドを実行すると、上記テストコードに記載された通りに実行され、スクリーンショットが同一リポジトリに保存されていると思います。
中身を改めてもらうと、```example-chromium.png```ではchromeを使用していたり、```example-firefox.png```ではfirefoxを使用しているのが確認できると思います。
![example-chromium.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255007/ca8d76d6-2d7b-5ac4-800d-74c20543a636.png)
![example-firefox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255007/ece3f99a-8ace-4c3a-e253-08c214b7864f.png)



## ヘッドレスモードを解除したいとき
デフォルトでヘッドレスモードでのテスト実行がされますが、実際にブラウザが立ち上がって動作するのが見たい場合にはblowserをlaunchする際に以下のように```{ headless: false}```を引数にわたして実行します。

```js
const browser = await playwright[browserType].launch({ headless: false});
```

## デバイスを変更したい時
playwrightではデバイスを変更してテストすることも可能です。
対象デバイスは[こちら](https://github.com/microsoft/playwright/blob/master/src/deviceDescriptors.ts)で確認することができます。

```js:example.js
const playwright = require('playwright');

//playwrightのdevicesにhttps://github.com/microsoft/playwright/blob/master/src/deviceDescriptors.tsの任意のものを指定することで、デバイスを取得できる。
const iphone11 = playwright.devices['iPhone 11'];

(async () => {
  for (const browserType of ['chromium', 'firefox', 'webkit']) {
    const browser = await playwright[browserType].launch();
    // newContextの引数にiphone11を渡してあげることで、デバイスを偽造できる。
    const context = await browser.newContext({
      ...iphone11
    });
    const page = await context.newPage();

    await page.goto('https://yourbrowser.is/');
    await page.screenshot({ path: `example-${browserType}.png`, fullPage: true });
    await browser.close();
  }
})();
```

デバイスの取得は以下のように取得することができます。

```js
const iphone11 = playwright.devices['iPhone 11'];
```

上記で取得したiphone11のオブジェクトをそのまま、newContextの引数として渡してあげることで、テストをiphone11で実施したのと同じ状態で動かすことができます。

```js
const context = await browser.newContext({
  ...iphone11
});
```

このテストコードは一番最初に行なった現在のブラウザやOSを確認するページにアクセスしてスクリーンショットを取得するテストなので、画像を確認してもらえれば、デバイスがiOSになっていることを確認できるかと思います。
![example-chromium.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255007/5bb38ff7-92a2-0379-feed-6da225cbef47.png)


## よく使うやつ
### ページ遷移

```js
// ページに移動
await page.goto('https:/example.com/');
```

### form入力
テキストインプットはfillによって実施をできます。
対象selectorに対して、第二引数の文字列を入力します。

```js
await page.fill('input[name="q"]', 'query');
```

### クリック
ボタンやリンクのセレクタを指定することでクリックできます。

```js
await page.click('input[name="btnK"]');
```

### セレクタがレンダリングされるまで待たせる
テストケースの中で、入力→submit→結果をスクリーンショットみたいなテストを行うときに、submit→結果をスクリーンショットのところがsubmitしてすぐスクリーンショットを撮ってしまうため、遷移する前にスクリーンショットをしてしまうのでうまくテストできないことがあります。そういう場合に遷移後のページのセレクタをwaitForSelectorで設定することで、セレクタが現れるまで待たせ、ちょっとスクリーンショットのタイミングを遅らせることができます。
ページ遷移系が含まれる時はかなり使います。

```js
// Wait for #search to appear in the DOM.
await page.waitForSelector('#result-stats', { state: 'attached' });
```

### domの選択
```js
// page.$(セレクタ)
await page.$('#something_id');
await page.$('.something_cls');
await page.$('input[name="hoge"]');
```

### domの選択(複数)
```js
// page.$$(セレクタ)
await page.$$('.something_cls');
await page.$$('p');
```

### 要素の抽出
page.evaluateメソッドを使うことで通常のJavaScript記述で処理できます。

```js
const result = await page.evaluate(() => {
  const nl = document.querySelectorAll('p');
  return nl;
});
```

### スクリーンショットを撮る

```js
//スクリーンショットを撮る
await page.screenshot({ path: `example.png`});
//スクリーンショットをフルページで撮る
await page.screenshot({ path: `example.png`, fullPage: true });
```

### 特定のリクエスト以外を無視する
page.routeとpage.continueとpage.abortを組み合わせて不必要なリクエストを無視させテストスピードを早めることができる。

#### 例1)画像をどうにか排除
```js
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

await page.route('**/*', route => {
  return route.request().resourceType() === 'image' ?
      route.abort() : route.continue();
});
//上の設定をした後でページ遷移
await page.goto('https://example.com');
```
#### 例2)対象のURL以外は無視
```js
await page.route('**/*', route => {
  if (route.url() == targetUrl)
    route.continue();
  else
    route.abort();
});
//上の設定をした後でページ遷移
await page.goto('https://example.com');
```

### Ajaxの読み込みを待ちたい
waitForNavigation　APIに {waitUntil:"networkidle"}を引数で渡すことで、ページの読み込みが完了する（jsによってページが生成される）まで待たせることができます。
ほかの要素は[こちら](https://playwright.dev/#version=v1.0.1&path=docs%2Fapi.md&q=pagewaitfornavigationoptions)で確認できます

```js
await page.waitForNavigation({waitUntil:"networkidle"});
```

### HTTP Authentication
```js
const context = await browser.newContext({
  httpCredentials: {
    username: 'hoge',
    password: 'fugafuga',
  },
});
const page = await context.newPage();
await page.goto('https://example.com');
```

# まとめ
ほぼほぼPuppeteerと同様の記述ができるので簡単にテストコードを作成できる上かなり豊富なAPIを用意してくれているので、やりたいことはかなりやれるかなという印象でした。

複数のブラウザに対してテストしたい場面はかなりあると思うので、その時の手段の一つとして考えておきたいですね。

PuppeteerもPlaywrightもそれぞれ強みを活かしていくことになると思うので、それぞれの動向を確認しつつ、場面にあったライブラリを選択するのが良いと思います。

# 参考
公式github: [Playwright](https://github.com/microsoft/playwright)

公式ページ: https://playwright.dev/