🎭

PlaywrightのAPIテストでトークンを使いまわす

に公開

はじめに

Playwrightは、WebアプリケーションのE2Eテスト行うツールとしての印象が強いですが、APIテストも実行することができます。ここでは、APIテストを書く際にトークンを使いまわす方法について説明します。

実現したいこと

Web API のよくある仕様として、ログイン用のAPIにユーザー認証情報を送信して、トークンを取得し、そのトークンを使って他のAPIを利用するというものがあります。
ログイン以外のAPIのテストをするときに、毎回ログイン用APIを実行してトークンを取得し、その後本来実行したいAPIを実行するのは、テストの実行時間が長くなります。
ここでは、ログイン用APIを一番最初に実行してトークンを取得して、保存しておき、他のAPIのテストでそのトークンを使いまわすという方法を実現したいこととします。

実現方法

カスタムフィクスチャの定義

まず、「認証情報」「トークンを保存するファイル」「トークン」をPlaywrightのFixturesをとして定義します。ここで定義した「トークン」のフィクスチャをテストケースで共通して使用することになります。

fixtures.ts
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を実行して、トークンを取得し、トークンを保存する部分を実装します。

tests/login.setup.ts
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ヘッダーが追加されます。

tests/api.test.ts
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.tsprojectsで、setupプロジェクトを定義し、apiプロジェクトのdependenciesに指定します。これにより、apiプロジェクトを実行する前に、setupプロジェクトが実行されます。

playwright.config.ts
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プロジェクトは、テストケース実行後に実行されるプロジェクトです。テストケース実行後に必要な処理があれば、ここに実装します。

tests/cleanup.teardown.ts
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);
    }
  });
});

ここでは、テストケース実行後にトークンを保存したファイルを削除する処理を実装しています。

GitHubで編集を提案

Discussion