🎭

CircleCIにPlaywrightを組み込んだ話

2023/12/14に公開1

こんにちは!saimyonです!
Social Databank Tech Blog Advent Calendar 2023の14日目です。

今回はCircleCIにPlaywrightを組み込んだ話をします🎭

きっかけ

私は弊社製品「Liny(リニー)」のQA担当として日々品質を格闘しております。
LinyのE2Eテストは現在、全て手動で実施しています。
そのため、新機能を入れた際にデグレが発生していたとしても、そのデグレが(ユニットテストでは発見できず)E2Eテストでしか発見できない場合、検証項目から漏れているとそのデグレに気付けない状況です。
ということで、この状況から脱却すべく、E2Eテストの自動化に取り組むことに。
LinyではCI/CDにCircleCIを使用しているので、そこにE2Eテストを組み込むこととなり、本記事を書くことと相成りました。
デグレ、本当に怖い。

やったこと

技術選定

まずは世の中の技術を知るところから。
E2Eテストフレームワークは数多く存在しますが、CypressPlaywrightに絞ってざっくりPros/Cons調査をしました。

項目 Cypress Playwright
動作速度
並行テスト 非対応 対応
マルチブラウザ Chrome, Edge, Firefox, Electron Chromium, Chrome, Edge, Firefox, Webkit (Safari)
マルチタブ 非対応 対応
マルチオリジン 非対応 対応
自動コード生成 便利そう?(Cypress Studioという機能を使うっぽい) 便利そう(要素特定がイケてないかも)
VRT 要プラグイン 対応
スクリーンショット 対応 対応

Playwrightのほうが速い&できることが多そうなので、Playwrightでいってみることに。

調査にあたっては下記記事を参考にさせていただきました。
https://www.estie.jp/blog/entry/2023/09/19/133816
https://zenn.dev/taiga533/articles/f6e1ef07a8676e
https://tech.techtouch.jp/entry/e2e-testing-tool

Playwrightを触ってみる

Playwrightの中身については、公式ドキュメントもありますし、検索すれば先人たちの素晴らしい記事が無限に出てくると思うのでささっと。
今回はLinyにログインするテストを書いてみます。


Linyのログインフォーム

Playwrightをインストールして

npm init playwright@latest

コードを書いて

example.spec.ts
import { test, expect } from '@playwright/test';

test("ログインテスト", async ({ page }) => {
  // Arrange
  const baseUrl = "開発環境のベースURL";
  const loginPath = "ログインページのパス";

  // Act
  await page.goto(baseUrl + loginPath);
  await page.getByPlaceholder("yourname").fill("user");
  await page.getByPlaceholder("パスワード").fill("password");
  await (await page.$("#authsubmit")).click();

  // Assert
  expect(await page.locator("h2").innerText()).toContain("トップ");
});

実行!

npx playwright test --ui

結果!

Running 1 test using 1 worker
·
  1 passed (3.8s)

こんな感じで
ログインページへ遷移->ユーザーID・パスワードを入力->ログインボタンを押下->ログイン!
のテストができるわけですね。便利〜〜〜
Linyはログイン後にトップページへ遷移するので、「トップ」とかかれたh2タグの存在確認によってログイン成功/失敗を判定しています。


h2のトップがありますね

また、上記のように実行時に--uiオプションをつけるとUI ModeでPlaywrightが起動します。
実際に動いている様子が目視できたり、スナップショットを撮ってくれたりと、自分の書いたコードが意図通りに動いているかを確認するのにとても便利です。(UI Modeはもっといろんな機能がありますがあまり使いこなせてはないです…)

本記事の本質は「CircleCIにPlaywrightを組み込むこと」なので、Playwright自体については一旦ここまでにして…
ここから実際にCircleCIに組み込んでいきます!

いざ組み込み

…とその前にまずは環境を整えるところから

まずはE2Eテスト対象の環境を構築します。
LinyはDocker環境で動いているので、docker-compose.ymlに設定を書いていきます。

docker-compose.yml
# 以下、E2Eテスト環境用
e2e-web:
  build:
    context: . # 基本的には本番の製品と同じ構成
    # ~~ 省略 ~~
  ports:
    - 8080:80
  environment:
    # ~~ 省略 ~~
    USE_RECAPTCHA: false
  depends_on: # DBなどはE2Eテスト用に作成
    e2e-db:
      condition: service_healthy
    e2e-redis:
      condition: service_healthy
    e2e-dynamo:
      condition: service_healthy
  networks:
    - dev-network
  profiles:
    - e2e-test

e2e-db:
  # ~~ 省略 ~~
  volumes:
    # データが永続化しないように設定
  networks:
    - dev-network
  profiles:
    - e2e-test

e2e-redis:
  # ~~ 省略 ~~
  volumes:
    # データが永続化しないように設定
  networks:
    - dev-network
  profiles:
    - e2e-test

e2e-dynamo:
  # ~~ 省略 ~~
  volumes:
    # データが永続化しないように設定
  networks:
    - dev-network
  profiles:
    - e2e-test

アプリサーバ(e2e-web)は、環境変数などは変えてますが基本的には実際の製品のLinyと同じにしています。
ただ、一点注意すべきはreCAPTCHAです。

みなさん一度は見たことがあるはずのreCAPTCHA
Linyではログイン時にreCAPTCHAを使用していますが、PlaywrightではreCAPTCHAを突破できません。そのためのreCAPTCHAなので当たり前ですね。
Linyにおいては開発用に環境変数でreCAPTCHAのON/OFFができるようになっているので、OFFになるようにしています。

DBについても、E2Eテスト用のもの(e2e-db, e2e-redis, e2e-dynamo)を作成します。
DBの設定もほぼ製品で使用しているものと同じなのですが、volumesでは、データを永続化しないように設定ファイル以外はマウントしないようにしています
その理由は、データの永続化をしないことで毎回同じデータセットでテストができ、再現性の高いテストが可能になるためです

Playwight実行環境も作成

Playwrightを走らせる(だけの)実行環境も作成します。

docker-compose.yml
playwright-runner:
  build:
    context: ./.docker/e2e
  working_dir: /testing
  environment:
    CI: true
  volumes:
    - ./:/testing
    - ./.docker/e2e/package.json:/testing/package.json
    - ./.docker/e2e/package-lock.json:/testing/package-lock.json
    - /testing/node_modules
  networks:
    - dev-network
./.docker/e2e/Dockerfile
FROM mcr.microsoft.com/playwright:v1.38.1-jammy

WORKDIR /testing

COPY package.json .
COPY package-lock.json .
RUN npm ci

イメージはPlaywrightのDockerイメージを指定しています。
本番のコードにPlaywrightインストール済み+テストコード(example.spec.ts)が存在するので、volumesはよしなにマウントしています。

テストコード修正

環境に合わせてテストコードも少し修正します

example.spec.ts
import { test, expect } from '@playwright/test';

test("ログインテスト", async ({ page }) => {
  // Arrange
  const baseUrl = "http://e2e-web"; // <-ここを変更
  const loginPath = "ログインページのパス";

  // Act
  await page.goto(baseUrl + loginPath);
  await page.getByPlaceholder("yourname").fill("user");
  await page.getByPlaceholder("パスワード").fill("password");
  await (await page.$("#authsubmit")).click();

  // Assert
  expect(await page.locator("h2").innerText()).toContain("トップ");
});

baseUrlhttp://e2e-webに変更しています。
これはe2e-webplaywright-runnerdev-networkという同一ネットワークに所属しているため、service名(e2e-web)で通信(コンテナ間通信)できるためです。

(Seeder作成+Makefile追加)

データセット用のSeederと、このあとのCircleCIのconfig.ymlを簡潔に書くためにMakefileも追加しましたが、詳細は割愛します。
Seederについては、今回はログインするために必要最低限なものだけを生成するようにしています。簡単のため、テストで使用するユーザーのユーザーIDはuser、パスワードはpasswordにしました。
Makefileについては、e2e-inite2e-webの初期化(ビルド、マイグレーション、シーダーによるデータ作成…)、e2ee2e-webを起動するような設定を追加しました。

CircleCIに組み込む

ここで満を持してCircleCIの設定です。
config.ymlに追記していきます。

config.yml
version: 2.1
# ~~ 省略 ~~ 
jobs:
  # ~~ 省略 ~~ 
  e2e-test:
    machine:
      image: ubuntu-2004:202201-02
      docker_layer_caching: true
    resource_class: large
    environment:
      DOCKER_BUILDKIT: 1
      COMPOSE_DOCKER_CLI_BUILD: 1
    steps:
      - attach_workspace:
          at: .
      - run: docker-compose build playwright-runner
      - run: make e2e-init
      - run: make e2e
      - run:
          name: Run Playwright E2E Tests
          command: docker-compose run playwright-runner npx playwright test
      - store_test_results:
          path: playwright_results.xml

workflows:
  version: 2
  general:
    jobs:
       # ~~ 省略 ~~ 
      - e2e-test:
        filters:
          branches:
            only:
              - /^e2e\/.*/ 

まずはjobs(e2e-test)についてです。ここが本記事の本題です。
ここで、

  • docker-compose build playwright-runner: playwright-runnerのビルド
  • make e2e-init: e2e-web(E2Eテスト対象環境)の初期化
  • make e2e: e2e-webの立ち上げ
  • docker-compose run playwright-runner npx playwright test: playwright-runnerを立ち上げ、E2Eテスト実施
  • store_test_results: 指定したパスにテスト結果書き込み

をするようにしています。(ここまで来るための準備が長かった分)意外とあっさりしてますね…!

またjobsfiltersについてですが、今はまだお試し段階なので、毎プッシュ/マージごとにE2Eテストが走らないよう、ブランチ名がe2e/で始まる場合にだけE2Eテストが走るようにしてみました。

これでCicleCIにPlaywrightによるE2Eテストが組み込めたはずです。
実際にe2e/から始まるブランチでPRを作って、動作確認してみましょう!

果たして結果は…!?

いけました!E2Eテスト実施&パスしてますね!

元気な子でよかった👶

おわりに

これでE2Eテストの自動化の基盤ができました。やったね!
今後はテストケースをガシガシ増やしていく予定です。
ただ、闇雲に増やしてもしょうがないので、

  • CUJ(Critical User Journey)を選定
  • そこからE2Eテストでしかテストできないものを抽出
  • それらを対象にテストケース作成->コード化

という感じで進めていこうと考えています。
また、PlaywrightはVRT(Visual Regression Test)もできるので、そちらもぜひ導入していきたいです。

それでは!よいE2E自動テストライフを!

ソーシャルデータバンク テックブログ

Discussion

tommy34tommy34

新しいバージョンのDockerだとBuildKitはデフォルトで有効化されていると思うのですが以下の環境変数の指定は必要なものでしょうか?

    environment:
      DOCKER_BUILDKIT: 1
      COMPOSE_DOCKER_CLI_BUILD: 1