こんにちは。
最近、Playwrightを使ってE2Eテストを書き始めたので実装していく中で困ったことや気づきなどを備忘録としてあれこれ書き残しています。
前提
Playwrightの公式サイトに従って用意したプロジェクトを使って試しています。
去年のアドベントカレンダーでも少し触れていたので、もう少し細かい手順で知りたい方はこちらもご覧ください。
Playwrightを使ってE2EテストやUI操作の自動化をやってみよう(環境構築編) - とことんDevOps | 日本仮想化技術のDevOps技術情報メディア
実際にテストを書いてみようと思います。
まとめて書く場合
まずは、何も考えずに認証処理からテストまでを一気に書いてみます。
import { test, expect } from '@playwright/test'; test('トップページの表示チェック', async ({ page }) => { await page.goto('https://xxxxxxxx/login'); await page.getByPlaceholder('メールアドレスを入力').click(); await page.getByPlaceholder('メールアドレスを入力').fill('{ここにメールアドレス}'); await page.getByPlaceholder('パスワードを入力').click(); await page.getByPlaceholder('パスワードを入力').fill('{ここにパスワード}'); await page.getByRole('button', { name: 'ログイン' }).click(); await page.waitForURL('https://xxxxxxxx/'); await expect(page.getByRole('heading', { name: 'xxxx' })).toBeVisible(); });
とりあえずテストを書いてみるくらいだとこんな感じで書いてもいいと思いますが、複数シナリオを扱うようになると冗長なコードが増えてくるのでコードの可読性が下がってしまいます。
test('テスト1', async ({ page }) => { // 認証処理 // テスト }); test('テスト2', async ({ page }) => { // 認証処理 // テスト }); test('テスト3', async ({ page }) => { // 認証処理 // テスト }); ...
実際にこのように書くことはないと思いますが、このタイミングで処理をまとめたくなります。
beforeAll()
やbeforeEach()
を使う場合
並列実行は今回は考えないことにします。
beforeAll()
やbeforeEach()
は、test( ... )
の前に1回だけ処理を実行したり、毎回実行したい処理を記述できる関数です。
beforeAll()
は、ファイル内に記述されたテストを実行する前に1回だけ実行されます。
test.beforeAll(async () => { // 認証処理 })
beforeEach()
は、ファイル内に記述されたテストごとに毎回最初に実行されます。
test.beforeEach(async () => { // 認証処理 })
この関数を使って共通化したとしても、ファイル分割をしたときにまた冗長な処理を記述する必要が出てきてしまいます。
また、Playwrightはブラウザコンテキストを採用しており、分離した環境でテストを実行するような仕組みになっているため、認証情報などの引き継ぎまわりも考慮する必要が出てきます。
もちろん自力で工夫して実装することもできるとは思いますが、今回はPlaywrightが用意してくれている仕組みを活用してみようと思います。
Playwrightの機能を活用する場合
この仕組みを使うと1度認証が成功したら認証情報をファイルに保持して次回以降のテストに活用するようになります。
その際に認証情報を保持する場所に準備をします。
mkdir -p playwright/.auth echo "\nplaywright/.auth" >> .gitignore
筆者が検証したときにうまくいかなかったので後で使用するファイルまで作成して済ませておきます。
touch playwright/.auth/user.json echo "{}" > playwright/.auth/user.json
次に認証処理を書いていきます。tests/auth.setup.ts
ファイルを作成してから次のようにコードを記述します。
import { test as setup, expect } from '@playwright/test'; const authFile = 'playwright/.auth/user.json'; setup('authenticate', async ({ page }) => { // ここに認証処理 await page.context().storageState({ path: authFile }); });
次にテスト実行時にこの認証ファイルが先に実行されるようにplaywright.config.ts
の設定ファイルに定義を追加します。
ブラウザの種類だけ同じような定義を追加してください。
import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ projects: [ // Setup project + { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', use: { ...devices['Desktop Chrome'], // Use prepared auth state. + storageState: 'playwright/.auth/user.json', }, + dependencies: ['setup'], }, { name: 'firefox', use: { ...devices['Desktop Firefox'], // Use prepared auth state. + storageState: 'playwright/.auth/user.json', }, + dependencies: ['setup'], }, ], });
これで準備は整いました。
実際に実行してみようと思います。Playwrightではいくつか実行方法がありますが、今回はUIモードで画面上で進捗が見れるようにして実行します。
npx playwright test --ui
新しくウィンドウが起動したら左端の三角ぼボタンを押して全てのテストを実行します。
すると画像のように先ほど書いた認証のファイルから実行されているのがわかると思います。
これで認証機能を外側に切り出してPlaywrightの仕組みを使っていい感じに実行させることができるようになりました。
おわりに
認証が必要なサービスのテストをする際には避けては通れない処理ですが、今回のようにPlayWightの機能を使うことでいい感じに認証機能を実行しれくるようになりました。
Playwrightの良さでもあるのですがテストごとに分離したブラウザを立ち上げるため、過去のブラウザ設定や認証情報に左右されずクリーンな環境でテストできることはいいことです。
その反面、認証情報などのケアをしないと毎回ログインをすることになるので、今回のようにPlaywrightの仕組みを活用することでいい感じに処理してくれるのはいいですね。
今後もテストは積極的に書いていくと思うので、詰まったことや気づいたことをあれこれブログで発信できたらと思います。