ChromiumベースのアプリケーションでCodeceptJSを使ったE2Eテストをする方法

# Puppeteer Connectを使う

[Puppeteer Connect](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions)を使うと、**Remote Debugging Port**を通して、Devtools Protocolを用いたブラウザの操作ができます。

そこに[CodeceptJS](https://codecept.io/)を組み合わせて、E2Eテストをします。

ElectronなどのChromiumベースのフレームワークで、CodeceptJSが要求するChromiumのAPIが用意されていれば使えます。

今回は、C#向けのChromiumバインディングである、**CefSharp**を用いて説明していきます。

## Remote Debugging Portの設定

[CefSharp](https://cefsharp.github.io/)についてあまり詳しくは書きませんが、ちょっと試してみたい場合はこちらのリポジトリを試してみてください。

https://github.com/cefsharp/CefSharp.MinimalExample

まずは、Remote Debugging Portを設定します。

```csharp
var settings = new CefSettings()
{
    RemoteDebuggingPort = 9222
};
```

起動すると、

http://localhost:9222

にアクセスできます。

右がChrome Devtoolsの画面で、左がCefSharpで起動したアプリです。

![chrome devtools.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169861/41ae65c2-b3df-34cf-82e9-fccb03ca7306.gif)

右のChrome DevtoolsをRemote Debugging Port経由で操作します。

## ブラウザの自動操作

puppeteerを使って、既存のChromiumインスタンスに接続します。

http://localhost:9222/json/version

にアクセスすると、接続のための情報が取れます。

```json
{
   "Browser": "Chrome/75.0.3770.100",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
   "V8-Version": "7.5.288.23",
   "WebKit-Version": "537.36 (@cd0b15c8b6a4e70c44e27f35c37a4029bad3e3b0)",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/e463d684-e9c0-4c81-91e3-b29a685d238d"
}
```

使うのは、`webSocketDebuggerUrl`のみです。

このエンドポイントを通して、puppeteerで接続します。

```bash
npm install --save-dev puppeteer request
```

`request`モジュールは、先ほど書いたJSONを取ってくるのに使います。

```js
const puppeteer = require("puppeteer");

const request = require("request");

async function main() {
  const { webSocketDebuggerUrl } = await new Promise((resolve, reject) => {
    request("http://localhost:9222/json/version", function(err, res, body) {
      if (err) {
        reject(err);
      } else {
        resolve(JSON.parse(body));
      }
    });
  });

  puppeteer
    .connect({
      browserWSEndpoint: webSocketDebuggerUrl
    })
    .then(async browser => {
      const pages = await browser.pages();

      const page = pages[0];

      await page.evaluate(() => {
        const input = document.querySelector("input[title=検索]");

        input.value = "hello world";
      });

      browser.disconnect();
    });
}

main();
```

前半は、`request`モジュールで接続情報を取得していて、

後半で、`puppeteer.connect`を用いてChromiumインスタンスに接続し、Google検索欄に`'hello world'`と入れています。

![browser automation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169861/9ae30dac-6140-a173-dd7c-0a2e3d661912.gif)

右がVSCodeで、Node.jsのスクリプトを実行すると、左側のアプリケーションで入力欄に「`'hello world'`」と入ったことがわかります。

なんか、ブラウザのウィンドウサイズがおかしくなってスクロールが出ていますが、

気になる場合は`puppeteer.connect()`のオプションの`defaultViewPort`で幅と高さを好みのものに指定しましょう。

```js
  puppeteer
    .connect({
      browserWSEndpoint: webSocketDebuggerUrl,
      defaultViewport: {
        width: 1024,
        height: 700
      }
    })
```

これで、ブラウザの自動操作ができました。

# CodeceptJSを使いE2Eテストをする

最後に、CodeceptJS ＋ Puppeteerです。

```bash
npm install --save-dev codeceptjs
```

codeceptjsをセットアップします

```bash
npx codeceptjs init
```

テストドライバーはもちろんpuppeteerを選んでください。

他はお好みで。。。

CodeceptJSを使って、Cefsharpブラウザを操作するには、WebSocketのエンドポイントを指定する必要があります。

下記のように、`codecept.conf.js`で、Helperに指定します。

```js:codecept.conf.js
exports.config = {
  helpers: {
    Puppeteer: {
      url: "http://localhost",
      chrome: {
        browserWSEndpoint:
          "ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a"
      }
    }
  },
  ...
};
```

今度は、http://localhost:9222/json/versionに同期的にアクセスし、WebSocketのエンドポイントを取得しましょう。

```bash
npm install sync-request --save-dev
```

`sync-request`は、同期的にHTTPリクエストができるモジュールです。

このように書き直します。

```js:codecept.conf.js
const request = require("sync-request");
const res = request("GET", "http://localhost:9222/json/version");

const { webSocketDebuggerUrl } = JSON.parse(res.getBody());

exports.config = {
  tests: "./*_test.js",
  output: "./output",
  helpers: {
    Puppeteer: {
      url: "http://localhost",
      chrome: {
        browserWSEndpoint: webSocketDebuggerUrl
      }
    }
  },
  include: {
    I: "./steps_file.js"
  },
  bootstrap: null,
  mocha: {},
  name: "cefsharp-puppeteer-connect",
  translation: "ja-JP"
};
```

これでOKです。

あとはテストを書くだけです。

下記のコマンドでテストを生成できます。

```bash
npx codeceptjs gt
```

生成されたテストを下記のように変更します。

```js
Feature("search input test");

Scenario("type hello world", I => {
  I.fillField("input[title=検索]", "hello world");
});
```

すっきりしすぎて笑えました。

あとはテストを実行するだけです。

```bash
npx codeceptjs run --steps
```

![e2e testing.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169861/53a91b50-6811-3f87-fed2-2850d25f6ffa.gif)

しっかり入力欄にタイプされてますね。

これでCefsharpでE2Eテストができました。

# 終わりに

Chromiumベースであれば、Puppeteerから接続できるので、後は同じです。

Electronでもできると思います。

Cefsharpは、.NET系だと一番メジャーだと勝手に思っているんですが、今までE2Eテストに関する資料を見つけられずにいましたが、

色々漁っていた時に、こちらの記事を見つけて、希望を見出しました。

https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja

GoogleのWeb ToolingによるDeveloper支援の充実さを感じました。

# Special Thanks

最後に、1つだけ紹介させてください。

今回初めて、Qiita記事にGIFアニメーションを導入しました。

べたべた画像を張り付けるよりも記事の可読性が上がったと思います。

GIFの作成に、ScreenToGifがめちゃくちゃ役に立ちました。

- ScreenToGif - https://www.screentogif.com

画面をキャプチャし、簡単にGIFが作れます。

是非、お試し下さい。