こんにちは。ニフティ株式会社のyamanakaです。
所属しているチームでPlaywrightのフィクスチャを導入したため、備忘録も兼ねて調べた内容やフィクスチャ概要から実際の使用例(@nifty紹介特典から紹介コード払い出し)とともに紹介します。
はじめに
ウェブアプリケーションの自動テストは、品質保証プロセスにおいて不可欠な要素となっています。その中で、Microsoftが開発したPlaywrightは、強力で使いやすいクロスブラウザテスト自動化ツールとして注目を集めています。
Playwrightは、Chromium(Chrome、Edge)、Firefox、WebKitベースのブラウザに対応し、JavaScriptやTypeScriptを使用して直感的なAPIを提供します。これにより、高速で信頼性の高いE2Eテストを簡単に作成できます。
しかし、効率的なテスト環境を構築するには、単にテストケースを書くだけでは不十分です。ここで重要な役割を果たすのが「フィクスチャ」です。
フィクスチャとは
フィクスチャとは、テストの実行に必要な前提条件や環境を設定するためのコードのことです。Playwrightにおいて、フィクスチャは以下のような役割を果たします。
1. テスト環境の一貫性を保証
2. コードの重複を削減
3. テストの可読性と保守性を向上
4. テスト実行の効率化
例えば、ブラウザの起動、ページの読み込み、ユーザーログインなど、多くのテストケースで共通して必要な操作をフィクスチャとして定義することで、テストコードをクリーンに保ち、効率的に管理できます。
フィクスチャの定義
Playwrightでは、test.beforeEach()
やtest.beforeAll()
などのフックを使用してフィクスチャを定義します。一般的な形式は以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 |
javascript import { test as base } from '@playwright/test'; export const test = base.extend({ myFixture: async ({ }, use) => { // フィクスチャのセットアップ const value = 'フィクスチャの値'; // フィクスチャの使用 await use(value); }, }); |
別ファイルでブラウザ、コンテキスト、ページフィクスチャをそれぞれ作成します、そしてこのフィクスチャを使用するテストファイルでは、以下のようにインポートして使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
javascript // myFixtures.js import { test as base, chromium } from '@playwright/test'; export const test = base.extend({ page: async ({ }, use) => { const browser = await chromium.launch(); const context = await browser.newContext(); const page = await context.newPage(); await use(page); await context.close(); await browser.close(); }, }); |
1 2 3 4 5 6 7 8 |
javascript // myTest.spec.js import { test } from './myFixtures'; test('フィクスチャを使用するテスト', async ({ page }) => { await page.goto('https://example.com'); // テストロジック }); |
上記により、基本的なブラウザ、コンテキスト、ページフィクスチャを使いこなすことで、テストの基盤を強固にできます。
カスタムフィクスチャの作成
基本的なフィクスチャに加えて、プロジェクト固有のニーズに合わせてカスタムフィクスチャを作成することができます。
これにより、テストコードの再利用性と管理性をさらに向上させることができます。
ユーザー認証とAPIレスポンスのモック化を例にあげます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
javascript import { test as base } from '@playwright/test'; export const test = base.extend({ loggedInPage: async ({ page }, use) => { // ログイン処理 await page.goto('https://example.com/login'); await page.fill('#username', 'testuser'); await page.fill('#password', 'testpass'); await page.click('#login-button'); await use(page); }, // APIモックを設定するフィクスチャ mockApi: async ({ page }, use) => { // APIリクエストのモックを設定 await page.route('**/api/dashboard', route => { route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ username: 'testuser', lastLogin: '2023-04-01' }) }); }); // モック設定済みのページをテストに提供 await use(page); }, // ログイン済みかつAPIモック適用済みの環境を提供するフィクスチャ preparedTestEnvironment: async ({ loginPage, mockApi }, use) => { // loginPage に mockApi の設定を適用 await mockApi(loginPage); // 準備完了した環境をテストに提供 await use(loginPage); }, }); |
この組み合わせたフィクスチャを使用するテストは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
javascript import { test } from './myFixtures'; // テスト test('ダッシュボードテスト', async ({ preparedTestEnvironment }) => { // 実際のダッシュボードページに遷移 await preparedTestEnvironment.goto('https://example.com/dashboard'); // ここで 'https://example.com/api/dashboard' にリクエストを送る // テストアサーションなど }); |
遷移する処理とAPIリクエストのモックを別々の概念として考えながら組み合わせてユニットテストを構築すると良いのかなと記事にしながら思いました。
フィクスチャのスコープ
Playwrightでは、フィクスチャのスコープを適切に設定することで、テストの効率と信頼性を向上させることができます。
テストファイルスコープ
テストファイル内でのみ使用するフィクスチャは、そのファイル内で定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
javascript import { test as base } from '@playwright/test'; const test = base.extend({ localFixture: async ({}, use) => { // このファイル内のテストでのみ使用可能なフィクスチャ await use('local value'); }, }); test('ローカルフィクスチャを使用', async ({ localFixture }) => { console.log(localFixture); // 'local value' }); |
グローバルスコープ
プロジェクト全体で使用するフィクスチャは、専用のファイル(例:fixtures.ts
)で定義し、インポートして使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
javascript // fixtures.ts import { test as base } from '@playwright/test'; export const test = base.extend({ globalFixture: async ({}, use) => { await use('global value'); }, }); // test.spec.ts import { test } from './fixtures'; test('グローバルフィクスチャを使用', async ({ globalFixture }) => { console.log(globalFixture); // 'global value' }); |
非同期フィクスチャの扱い方
Playwrightのフィクスチャは非同期操作を効果的にサポートしており、async/awaitを使用して簡単に非同期処理を組み込むことができます。
基本的な非同期フィクスチャ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
javascript import { test as base } from '@playwright/test'; export const test = base.extend({ asyncData: async ({}, use) => { const data = await fetchDataFromApi(); await use(data); }, }); test('非同期データを使用', async ({ asyncData }) => { console.log(asyncData); // テストロジック }); |
エラーハンドリング
エラーが発生した場合のハンドリング処理も忘れず作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
javascript import { test as base } from '@playwright/test'; export const test = base.extend({ errorProneFixture: async ({}, use) => { try { const result = await fetchDataFromApi(); await use(result); } catch (error) { console.error('フィクスチャの初期化中にエラーが発生しました:', error); throw error; // テストを失敗させる } }, }); |
フィクスチャの依存関係
フィクスチャは他のフィクスチャに依存することができ、これによって複雑なテスト環境を構築できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
javascript import { test as base } from '@playwright/test'; export const test = base.extend({ config: async ({}, use) => { // 設定をロード const config = await loadConfig(); await use(config); }, apiClient: async ({ config }, use) => { // configフィクスチャを使用してAPIクライアントを初期化 const client = new ApiClient(config.apiUrl); await use(client); }, loggedInUser: async ({ page, apiClient }, use) => { // apiClientフィクスチャを使用してユーザーを作成 const user = await apiClient.createUser(); // ページにログイン await page.goto(config.loginUrl); await page.fill('#username', user.username); await page.fill('#password', user.password); await page.click('#login-button'); await use(user); }, userWithData: async ({ loggedInUser, apiClient }, use) => { // ログイン済みユーザーにデータを追加 const userData = await apiClient.addUserData(loggedInUser.id, { posts: ['Test Post'] }); await use({ ...loggedInUser, ...userData }); }, }); test('ユーザーデータのテスト', async ({ userWithData, page }) => { await page.goto('https://example.com/profile'); const postTitle = await page.textContent('.post-title'); // 検証 expect(postTitle).toBe('Test Post'); expect(userWithData.posts).toContain('Test Post'); }); |
この例では、複数のフィクスチャが互いに依存関係を持っています。
1. apiClient
は config
に依存
2. loggedInUser
は page
と apiClient
に依存
3. userWithData
は loggedInUser
と apiClient
に依存
実践的な例:払い出したURLに遷移するテスト
ニフティでは光回線サービスをお友達に紹介すると、紹介した方&紹介された方それぞれにニフティポイントをプレゼントするサービスがあります。(@nifty紹介特典)
今回は上記サービスから紹介コードを払い出し、紹介ページから申込画面に遷移する内容を例にあげます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import { test as base } from '@playwright/test'; import { expect } from "@playwright/test"; export const test = base.extend({ mainPage: async ({ page }, use) => { await page.goto('https://setsuzoku.nifty.com/syokai/index.htm'); await use(page); }, searchUrl: async ({ mainPage }, use) => { await mainPage.getByRole('link', { name: '紹介する' }).click(); // ページにログイン await mainPage.fill('#id_user_id', 'xxxxxxxx'); await mainPage.click('#next'); await mainPage.fill('#id_pw', 'xxxxxxxx'); await mainPage.click('#login'); const url = await mainPage.evaluate(() => { const element = document.querySelector('.shokaiCode_signUp a'); return element ? element.href : null; }); await mainPage.goto(url); // url を返すように変更 await use({ page: mainPage, url: url }); }, }); test('払い出されたURLに遷移', async ({ mainPage, searchUrl }) => { const { url } = searchUrl; // URLを確認する await expect(mainPage).toHaveURL(url); }); |
おわりに
Playwrightのフィクスチャを導入することで、コードの再利用や共通化することができるためチームとしても一貫したテストを採用しやすくなります。
APIレスポンスのモック化について調べながらコードを書きましたが、ユニットテストに対して柔軟で安定したテスト環境が構築できると思いました。