[QA Wolf] ブラウザ操作からテストコードを生成する? 話題のテストライブラリ QA Wolfを使ってみる


先月くらいにTwitterで少し話題になっていた、ブラウザテストの自動生成ライブラリ QA Wolfを使ってみたので紹介します。

QA Wolfとは?

ブラウザ上の操作から、Puppetterと、Jestを使ったブラウザテストを生成するライブラリです。
QA WolfのGitHubに掲載されている以下GIFのように簡単にテストコードを生成できます。

  • ボイラープレートを書く必要がない
  • 同期操作と、スマートセレクタで安定的なテストを構築できる
  • 複雑な操作のテストも手軽にコード化できる
  • CIのセットアップも簡単に
  • CIで自動的にIFや、MP4を生成してデバックも簡単

という特徴があります。

テスト対象のVue.jsアプリ

今回はテスト用に作った簡易なVue.jsアプリをサンプルにQA Wolfを実行します。

リポジトリはこちら。
https://github.com/kawamataryo/sandbox-vue-qa-wolf

テストする機能はこちらです。

fullnameの表示
Homeページで、fistnameとlastnameを入力すると、それらを結合したfullnameを表示する

これをQA Wolfでテストしていきます。

QA Wolfでのテスト

インストール

テスト対象のディレクトリでQA Wolfをインストールします。

yarn add qawolf

ブラウザテストの作成

次にブラウザテストを作成します。

QA Wolfではブラウザでテストを実行するので、まずローカルサーバーでVueアプリを起動します。

yarn run serve --port 8088

次にfullnameの表示のテストを作成します。

npx qawolf create http://localhost:8088 fullmessage

すると、Chromiumのブラウザが起動するので、firstnameとlastnameに文字列を入力します。

入力した後、コマンドを入力したシェルに戻り、YEnterを押します
以下のような表示がでれば完了です。

  ✔ Capturing browser actions for "fullmessage.test.js"
  ✔ Saving 
"/Users/kawamataryou/.ghq/github.com/kawamataryo/sandbox-vue-qa-wolf/.qawolf/tests/fullmessage.test.j
s"

2つのファイルが自動作成されているはずです。

.qawolf
├── tests
│   └── fullmessage.test.js
└── selectors
   └── fullmessage.json

中身を見ていきましょう。

まず、selectors/fullmessage.json

[
 {
  "index": 0,
  "css": "[data-test='firstname-input']"
 },
 {
  "index": 1,
  "css": "[data-test='firstname-input']"
 },
 {
  "index": 2,
  "css": "[data-test='firstname-input']"
 },
 {
  "index": 3,
  "css": "[data-test='lastname-input']"
 }
]

事前にVue.jsアプリ側でtemplateに埋め込んでいたdata-test="xxx"を認識して、セレクタが生成されています。
こちらをテストコードから参照できます。
(data-test="xxx"を入力しない場合は、操作単位のNodeを解析して自動的に親ノード、class、idより一意に決まるセレクタを生成してくれます。解説は、こちらをご覧ください。)

次にtests/fullmessage.test.js

fullmessage.test.js
const { launch } = require("qawolf");
const selectors = require("../selectors/fullmessage");

describe('fullmessage', () => {
  let browser;

  beforeAll(async () => {
    browser = await launch({ url: "http://localhost:8088/" });
  });

  afterAll(() => browser.close());

  it('can click "First name" input', async () => {
    await browser.click({ css: "[data-test='firstname-input']" });
  });

  it('can type into "First name" input', async () => {
    await browser.type({ css: "[data-test='firstname-input']" }, "kawamata");
  });

  it('can Tab', async () => {
    await browser.type({ css: "[data-test='firstname-input']" }, "↓Tab↑Tab");
  });

  it('can type into "Last name" input', async () => {
    await browser.type({ css: "[data-test='lastname-input']" }, "ryo");
  });
});

操作単位で、テストが書かれてしかもコメントまで自動で生成されていることがわかります。
すごい..。

テストの修正、assertionの追加

自動生成のコードだと
* タブ移動などもコード化されていて余計(これが必要になることもあると思います)
* assertionがない
という問題があるので修正します。

以下のように修正しました。

これでfullname-fieldに値が入力されていることを検証できます。

fullmessage.test.js
const { launch } = require("qawolf");
const selectors = require("../selectors/fullname");

describe('showFullname', () => {
  let browser;

  beforeAll(async () => {
    browser = await launch({ url: process.env.QAW_URL || "http://localhost:8084/" });
  });

  afterAll(() => browser.close());

  it('Show fullname', async () => {
    await browser.type({ css: "[data-test='firstname-input']" }, "kawamata");
    await browser.type({ css: "[data-test='lastname-input']" }, "ryo");

    const fullnameElement = await browser.find({css: "[data-test='fullname-field']"});
    const fullnameText = await fullnameElement.evaluate( n => n.innerText);

    expect(fullnameText).toBe("kawamata ryo");
  });
});

これでテストを実行して通ればfullnameのテストは完了です。

npx qawolf test fullname

Circle CIでのCI環境構築

次はCircle CIの設定をします。

コマンド一つでconfig.ymlの生成ができます。

npx qawolf circleci

以下ファイルが生成されるはずです。

.circleci
└── config.yml

自動生成されたconfig.ymlに、Vue.js アプリの起動等を追記します。
最終的に以下のようにしました。

config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/node:12.14.0-browsers
    steps:
      - checkout

      - restore_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}

      - run:
          command: npm install

      - save_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules

      - run:
          name: Start test with QA Wolf
          command: |
            ## Start local server
            yarn serve & npx wait-on http://localhost:8080
            ## Start test
            npx qawolf test
          environment:
            QAW_ARTIFACT_PATH: /tmp/artifacts
            QAW_URL: http://localhost:8080

      - store_artifacts:
          path: /tmp/artifacts

ポイントは、環境変数でQAW_URLを指定して、portを固定している点です。
環境変数にQAW_URLが指定されている場合は、テストもそちらを参照するように修正しましょう。

fullname.test.js
  beforeAll(async () => {
-    browser = await launch({ url: "http://localhost:8088/" });
+    browser = await launch({ url: process.env.QAW_URL || "http://localhost:8088/" });
  });

これでCircle CIの連携をした上でリポジトリにcommit、pushすればテストが実行されるはずです。

実行が完了すると自動的にArtifactsにテスト実行結果のログ(GIF、mp4)が生成されます。
テストが失敗した際に、こちらから原因を探ることも容易です。

(実際に撮影されたGIF)

終わりに

以上QA Wolfの紹介でした。
操作をイメージしながらテストコードを書くのではなく、操作そのものがテストコードになる。というのはとても新鮮な体験でした。
また、公式ドキュメントが非常にわかりやすいのも良いです。

本プロダクトで利用するには、まだまだ検証しなくてはならない点多々あるのですが、個人開発で使う分にはかなり良さそうです。
今後も活用していきたいです。