CircleCIにPlaywrightを組み込んだ話
こんにちは!saimyonです!
Social Databank Tech Blog Advent Calendar 2023の14日目です。
今回はCircleCIにPlaywrightを組み込んだ話をします🎭
きっかけ
私は弊社製品「Liny(リニー)」のQA担当として日々品質を格闘しております。
LinyのE2Eテストは現在、全て手動で実施しています。
そのため、新機能を入れた際にデグレが発生していたとしても、そのデグレが(ユニットテストでは発見できず)E2Eテストでしか発見できない場合、検証項目から漏れているとそのデグレに気付けない状況です。
ということで、この状況から脱却すべく、E2Eテストの自動化に取り組むことに。
LinyではCI/CDにCircleCIを使用しているので、そこにE2Eテストを組み込むこととなり、本記事を書くことと相成りました。
デグレ、本当に怖い。
やったこと
技術選定
まずは世の中の技術を知るところから。
E2Eテストフレームワークは数多く存在しますが、CypressとPlaywrightに絞ってざっくりPros/Cons調査をしました。
項目 | Cypress | Playwright |
---|---|---|
動作速度 | 並 | 速 |
並行テスト | 非対応 | 対応 |
マルチブラウザ | Chrome, Edge, Firefox, Electron | Chromium, Chrome, Edge, Firefox, Webkit (Safari) |
マルチタブ | 非対応 | 対応 |
マルチオリジン | 非対応 | 対応 |
自動コード生成 | 便利そう?(Cypress Studioという機能を使うっぽい) | 便利そう(要素特定がイケてないかも) |
VRT | 要プラグイン | 対応 |
スクリーンショット | 対応 | 対応 |
Playwrightのほうが速い&できることが多そうなので、Playwrightでいってみることに。
調査にあたっては下記記事を参考にさせていただきました。
Playwrightを触ってみる
Playwrightの中身については、公式ドキュメントもありますし、検索すれば先人たちの素晴らしい記事が無限に出てくると思うのでささっと。
今回はLinyにログインするテストを書いてみます。
Linyのログインフォーム
Playwrightをインストールして
npm init playwright@latest
コードを書いて
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
に設定を書いていきます。
# 以下、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を走らせる(だけの)実行環境も作成します。
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
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
はよしなにマウントしています。
テストコード修正
環境に合わせてテストコードも少し修正します
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("トップ");
});
baseUrl
をhttp://e2e-web
に変更しています。
これはe2e-web
とplaywright-runner
がdev-network
という同一ネットワークに所属しているため、service名(e2e-web
)で通信(コンテナ間通信)できるためです。
(Seeder作成+Makefile追加)
データセット用のSeederと、このあとのCircleCIのconfig.yml
を簡潔に書くためにMakefileも追加しましたが、詳細は割愛します。
Seederについては、今回はログインするために必要最低限なものだけを生成するようにしています。簡単のため、テストで使用するユーザーのユーザーIDはuser
、パスワードはpassword
にしました。
Makefileについては、e2e-init
でe2e-web
の初期化(ビルド、マイグレーション、シーダーによるデータ作成…)、e2e
でe2e-web
を起動するような設定を追加しました。
CircleCIに組み込む
ここで満を持してCircleCIの設定です。
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
: 指定したパスにテスト結果書き込み
をするようにしています。(ここまで来るための準備が長かった分)意外とあっさりしてますね…!
またjobs
のfilters
についてですが、今はまだお試し段階なので、毎プッシュ/マージごとにE2Eテストが走らないよう、ブランチ名がe2e/
で始まる場合にだけE2Eテストが走るようにしてみました。
これでCicleCIにPlaywrightによるE2Eテストが組み込めたはずです。
実際にe2e/
から始まるブランチでPRを作って、動作確認してみましょう!
果たして結果は…!?
いけました!E2Eテスト実施&パスしてますね!
元気な子でよかった👶
おわりに
これでE2Eテストの自動化の基盤ができました。やったね!
今後はテストケースをガシガシ増やしていく予定です。
ただ、闇雲に増やしてもしょうがないので、
- CUJ(Critical User Journey)を選定
- そこからE2Eテストでしかテストできないものを抽出
- それらを対象にテストケース作成->コード化
という感じで進めていこうと考えています。
また、PlaywrightはVRT(Visual Regression Test)もできるので、そちらもぜひ導入していきたいです。
それでは!よいE2E自動テストライフを!
Discussion
新しいバージョンのDockerだとBuildKitはデフォルトで有効化されていると思うのですが以下の環境変数の指定は必要なものでしょうか?