💡

Playwrightで複数ユーザーを切り替えるE2Eテストの実装方法

2024/10/07に公開

はじめに

この記事ではPlaywrightを使ってE2Eテストを実装する際に複数ユーザーの切り替えを容易にする方法を紹介します。
この手法を使うことでテストのパフォーマンスやメンテナンス性向上が期待できると思います。

Playwrightとは?

Playwrightは、ブラウザ自動化ツールであり、クロスブラウザテストやE2E(エンドツーエンド)テストを容易に実現できるライブラリです。
詳しくは公式ドキュメントを参照してください。

動機

E2Eテスト対象のアプリケーションでロールベースの認可が行われているケースは少なくないと思います。
例えば「一般ユーザーと管理者」、「組織オーナーと所属メンバー」のように。
E2Eテストの各テストケースでログイン操作や権限変更等を行うことで異なるロールに対するテストを行うことはできます。
しかしながらそれらのテスト対象ではない処理が増えるのはあまり好ましくないです。
可能な限りテスト対象に絞った実装である方がメンテナンス性は高いと言えます。

globalSetupとfixturesとは?

複数ユーザーを用いたテストの実装方法に入る前に、PlaywrightのglobalSetupとfixturesについて確認します。

globalSetupの設定方法

playwright.config.tsで以下のようにglobalSetupを設定すると、対象のスクリプトをテスト起動時に1度だけ実行できます。
この記事の内容のユーザーごとのログイン処理のように、テスト全体で共通の初期化が必要な場合に役立ちます。

globalSetup.ts

export default async function globalSetup() {
  // 初期化処理
}

playwright.config.ts

export default defineConfig({
  globalSetup: require.resolve('./globalSetup'),
})

fixturesの活用

以下のようにPlaywrightのコンポーネントをラップすることで任意のオブジェクトをテストケースに渡すことができます。
このfixturesの初期化処理はテストケース毎に毎回実行されます。

fixtures.ts

import { type Page, type Browser, test as base } from '@playwright/test'

type CustomFixtures = {
  googlePage: Page
}

export * from '@playwright/test'

export const test = base.extend<CustomFixtures>({
  googlePage: async ({ page }, use) => {
    await page.goto('https://www.google.co.jp/')
    await use(page)
  },
})

sample.spec.ts

import { test } from './fixtures'

test('サンプルテスト', async ({ googlePage }) => {
  // googlePageを使ったテスト
})

複数ユーザーを用いたE2Eテスト実装方法

大まかな流れは以下になります。

  1. globalSetupで各ユーザーのログインを行う
  2. 各ユーザーのログイン後のCookieやLocalStorageの状態を保存する
  3. fixturesで各ユーザーのCookieやLocalStorageを復元しつつラップオブジェクトを生成する

globalSetupの実装

globalSetup.ts

async function login(email: string, password: string, roleName: string) {
  // 新しいブラウザを立ち上げてログインページへアクセスする
  const browser = await chromium.launch()
  const page = await browser.newPage()
  await page.goto('/login', { waitUntil: 'load' })

  // ログインフォームに情報を入力しログインを行う
  await page.getByLabel('E-Mail').fill(email || '')
  await page.getByLabel('Password').fill(password || '')
  await page.getByRole('button', { name: 'ログイン' }).click()
  await page.waitForURL('/myPage')

  // ログイン後のストレージの状態を保存する
  await page.context().storageState({ path: `./states/${roleName}.json` })
  await browser.close()
}

export default async function globalSetup() {
  // 管理者としてログイン
  await login('admin@example.com', 'password', 'admin')

  // ユーザーとしてログイン
  await login('user@example.com', 'password', 'user')
}

playwright.config.ts

export default defineConfig({
  globalSetup: require.resolve('./globalSetup'),
  use: {
    baseURL: 'http://127.0.0.1:3000',
  },
})

fixturesの実装

fixtures.ts

import { type Page, type Browser, test as base } from '@playwright/test'

type CustomFixtures = {
  adminPage: Page
  userPage: Page
}

export * from '@playwright/test'

export const test = base.extend<CustomFixtures>({
  adminPage: async ({ browser }, use) => {
    // globalSetupで保存した管理者ログイン後のデータを指定
    const context = await browser.newContext({
      storageState: './states/admin.json',
    })
    const page = await context.newPage()
    await use(page)
    await context.close()
  },
  userPage: async ({ browser }, use) => {
    // globalSetupで保存したユーザーログイン後のデータを指定
    const context = await browser.newContext({
      storageState: './states/user.json',
    })
    const page = await context.newPage()
    await use(page)
    await context.close()
  },
})

テストケースの実装

fixturesで定義したadminPage, userPageを使ってテストを実装できます。
それぞれログイン後の状態になっているので、テストケース内でログイン処理が不要です。
3つ目のサンプルのように、adminPage, userPage両方使用したテストも可能です。

sample.spec.ts

import { test } from './fixtures'

test.describe('管理者', () => {
  test('管理者だけのサンプルテスト', async ({ adminPage }) => {
    // 管理者権限でのテスト
  })
})

test.describe('ユーザー', () => {
  test('ユーザーだけのサンプルテスト', async ({ userPage }) => {
    // ユーザー権限でのテスト
  })
})

test('ユーザー権限変更テスト', async ({ adminPage, userPage }) => {
  // 1. userPageを使った権限変更前のテスト
  // 2. adminPageを使ってユーザーの権限変更
  // 3. userPageを使った権限変更後のテスト
})

Discussion