はじめに
Playwrightは、WebアプリケーションのE2Eテスト行うツールとしての印象が強いですが、APIテストも実行することができます。ここでは、APIテストを書く際にトークンを使いまわす方法について説明します。
実現したいこと
Web API のよくある仕様として、ログイン用のAPIにユーザー認証情報を送信して、トークンを取得し、そのトークンを使って他のAPIを利用するというものがあります。
ログイン以外のAPIのテストをするときに、毎回ログイン用APIを実行してトークンを取得し、その後本来実行したいAPIを実行するのは、テストの実行時間が長くなります。
ここでは、ログイン用APIを一番最初に実行してトークンを取得して、保存しておき、他のAPIのテストでそのトークンを使いまわすという方法を実現したいこととします。
実現方法
カスタムフィクスチャの定義
まず、「認証情報」「トークンを保存するファイル」「トークン」をPlaywrightのFixturesをとして定義します。ここで定義した「トークン」のフィクスチャをテストケースで共通して使用することになります。
import {test as base} from "@playwright/test";
import fs from "fs/promises";
import path from "path";
export {expect} from "@playwright/test";
type TestFixtures = {
credentials: {
username: string;
password: string;
};
authFile: string;
token: string;
};
export const test = base.extend<TestFixtures>({
// 認証情報
credentials: async ({}, use) => {
const credentials = {
username: process.env.USERNAME || "",
password: process.env.PASSWORD || "",
};
await use(credentials);
},
// トークンを保存するファイル
authFile: async ({}, use) => {
const authFile = path.resolve(`.auth/user.json`);
use(authFile);
},
// トークン
token: async ({authFile}, use) => {
const data = await fs.readFile(authFile, "utf-8");
const {token} = JSON.parse(data);
await use(token);
},
});
テストケースを実装するときには@playwright/test
からインポートしたtest
ではなく、ここで定義したtest
をインポートして使用します。
ログイン → トークンの保存
ログイン用のAPIを実行して、トークンを取得し、トークンを保存する部分を実装します。
import { test as setup, expect } from "./fixtures";
setup.describe("ログイン", () => {
setup("トークンを発行する", async ({request, credentials, authFile}) => {
const response = await request.post(`/login`, {
data: {
username: credentials.username,
password: credentials.password,
},
});
expect(response).toBeOK();
const body = await response.json();
expect(body).toHaveProperty("token");
await fs.writeFile(authFile, JSON.stringify(body));
});
});
ログイン時に必要な認証情報は、fixtures.ts
で定義したcredentials
フィクスチャを使用して取得します。トークンは、authFile
フィクスチャで指定したファイルに保存します。
なお、テストの前処理を実装するときは、慣例としてimport { test as setup }
として、setup
という名前でインポートします。
テストケースでのトークンの利用
保存したトークンをテストケースを利用します。
ここでは、トークンをAuthorization
ヘッダーにセットして、APIを実行する例を示します。
テストを実装するファイル内で、test.use
を使ってTestOptions.extraHTTPHeaders
の値を変更します。これにより、このファイル内でのすべてのリクエストにAuthorization
ヘッダーが追加されます。
import { test, expect } from "./fixtures";
test.use({
extraHTTPHeaders: {
Authorization: `Bearer ${token}`,
},
});
test.describe("check application is active", () => {
test("GET /api/health", async ({ request }) => {
const response =
await test.step("Act: request to API", async () => {
return await request.get(`/api/users`);
});
await test.step("Assert: validate response", async () => {
expect(response.status()).toBe(200);
const body = await response.json();
expect(body).toMatchObject({
active: true,
});
});
});
});
トークンの取得を一番最初に実行するように設定
テストケースを実行する前に、トークンを取得するように設定します。playwright.config.ts
のprojects
で、setup
プロジェクトを定義し、api
プロジェクトのdependencies
に指定します。これにより、api
プロジェクトを実行する前に、setup
プロジェクトが実行されます。
import {defineConfig} from "@playwright/test";
export default defineConfig({
projects: [
{
name: "setup",
testMatch: "tests/*.setup.ts",
teardown: "teardown",
},
{
name: "api",
testMatch: "tests/*.test.ts",
dependencies: ["setup"],
},
{
name: "teardown",
testMatch: "tests/*.teardown.ts",
},
],
});
ちなみに、teardown
プロジェクトは、テストケース実行後に実行されるプロジェクトです。テストケース実行後に必要な処理があれば、ここに実装します。
import { test as teardown } from "./fixtures";
import fs from "fs";
import fsAsync from "fs/promises";
teardown.describe("Clean files", () => {
teardown("delete authorized token", async ({authFile}) => {
if (fs.existsSync(authFile)) {
await fsAsync.rm(authFile);
}
});
});
ここでは、テストケース実行後にトークンを保存したファイルを削除する処理を実装しています。