はじめに
- テストごとにログイン画面へ遷移→ログインをしなくて済むようにログイン情報(cookie)を保持する
- テストに合わせてログインユーザーを使い分ける
上記を叶えるために行った対応内容の備忘録です。
複数ユーザーの管理方法はまだ整理できそうですが、一旦アウトプットしてます。
環境
- @playwright/test@1.54.1
対応の流れ
- 認証について、基本は公式を参照する
- 環境変数にログイン時に必要な情報を管理する
- 環境変数を格納するクラスファイルにUsersを追加する
-
tests/auth.setup.tsを作成する -
playwright.config.tsにログイン情報を保存するファイルの存在チェック・作成処理を追加する -
playwright.config.tsにsetupプロジェクトを追加する - 保存したログイン情報を使用して実行するプロジェクトに
storageStateとdependenciesを追加する
環境変数でログイン時に必要な情報を管理する
環境変数ファイルの設定などについては、先に下記を参照ください。
【Playwright】テストを実行する環境(DEV・STG)を切り替える #Playwright - Qiita
- 環境変数で下記を管理する。
- ログイン情報(ログインID・パスワード)
- ログイン情報を保存するファイルパス
DEV環境変数のサンプル
# デフォルトユーザー
LOGIN_USER=ui_test@sample.co.jp
LOGIN_PASSWORD=password
STATE_FILE_PATH=playwright/.auth/default-user/dev-login-state.json
# ダークモードユーザー
DARK_MODE_LOGIN_USER=ui_test002@sample.co.jp
DARK_MODE_LOGIN_PASSWORD=password
DARK_MODE_STATE_FILE_PATH=playwright/.auth/dark-mode/dev-login-state.json
# 管理するユーザー分追加する
...(省略)
STG環境変数のサンプル
# デフォルトユーザー
LOGIN_USER=ui_test@sample.co.jp
LOGIN_PASSWORD=password
STATE_FILE_PATH=playwright/.auth/default-user/stg-login-state.json
# ダークモードユーザー
DARK_MODE_LOGIN_USER=ui_test002@sample.co.jp
DARK_MODE_LOGIN_PASSWORD=password
DARK_MODE_STATE_FILE_PATH=playwright/.auth/dark-mode/stg-login-state.json
# 管理するユーザー分追加する
...(省略)
環境変数を格納するクラスにUsersを追加する
Env.Users.Default.LoginUserなど、参照しやすいように追加する。
export class Env {
static Users = {
Default: {
LoginUser: process.env.LOGIN_USER ?? 'ui_test@sample.co.jp',
LoginPassword: process.env.LOGIN_PASSWORD ?? 'password',
StateFilePath: process.env.STATE_FILE_PATH ?? 'playwright/.auth/default-user/dev-login-state.json',
},
DarkMode: {
LoginUser: process.env.DARK_MODE_LOGIN_USER ?? 'ui_test002@sample.co.jp',
LoginPassword: process.env.DARK_MODE_LOGIN_PASSWORD ?? 'password',
StateFilePath: process.env.DARK_MODE_STATE_FILE_PATH ?? 'playwright/.auth/dark-mode/dev-login-state.json',
},
};
}
tests/auth.setup.tsを作成する
テスト前に実行する認証関連の処理を実行するsetupを作成する。
以下は自分の環境で実装したもの。
import { test as setup } from "@playwright/test";
import { Env } from "environments/env";
import { login } from "utils/utils";
// 各ユーザーごとにstorage stateを保存します
for (const key of Object.keys(Env.Users)) {
const user = Env.Users[key];
setup(`ログイン処理: ${key}`, async ({ page }) => {
await login({ page, userId: user.LoginUser, password: user.LoginPassword });
// ログイン後のセッションを維持するため、cookieをstate.jsonに保存する
await page.context().storageState({ path: user.StateFilePath });
});
}
別ファイルに定義したログイン処理を実行、ログイン後のブラウザのストレージ状態を環境変数で定義したファイルへ保存している。
別ファイルのログイン処理
/** 環境設定のBASE_URLにpathを指定したurlを返します。 */
export function baseUrl(path: string) {
// HACK: Env.BaseUrlでUPDATEが取得できないので、現状process.env.BASE_URLを参照しています。
return `${process.env.BASE_URL}${path}`;
}
/** ログイン処理を行います。 */
export async function login({
page,
userId = Env.Users.Default.LoginUser,
password = Env.Users.Default.LoginPassword,
}: {
page: Page;
userId?: string;
password?: string;
}) {
await page.goto(baseUrl('/Auth/Login'));
await page.waitForSelector(INPUT_LOGIN_ID, { state: 'visible' });
await page.locator(INPUT_LOGIN_ID).click();
await page.locator(INPUT_LOGIN_ID).fill(userId);
await page.waitForSelector(INPUT_LOGIN_PASSWORD, { state: 'visible' });
await page.locator(INPUT_LOGIN_PASSWORD).click();
await page.locator(INPUT_LOGIN_PASSWORD).fill(password);
await page.waitForSelector(BTN_LOGIN, { state: 'visible' });
await page.locator(BTN_LOGIN).click();
await page.waitForURL((url) => url.toString().includes(baseUrl('/Top')));
}
playwright.config.tsにログイン情報を保存するファイルの存在チェック・作成処理を追加する
import "dotenv/config";
import * as fs from "fs";
import path from "path";
import { Env } from "./environments/env";
// NOTE: 環境変数を格納したクラスからpathを指定しています。環境変数未指定の場合、devを参照します。
require("dotenv").config({
path: path.resolve(__dirname, "./environments", `.env.${Env.Target}`),
});
// NOTE: ログイン情報ファイルの存在チェック、なければ作成します。
Object.keys(Env.Users).forEach(key => {
let user = Env.Users[key];
if (!fs.existsSync(user.StateFilePath)) {
fs.writeFileSync(user.StateFilePath, "{}");
}
});
...(省略)
playwright.config.tsにsetupプロジェクトを追加する
ログインなどの認証処理を実行するためのプロジェクトsetupをprojectsに追加する。
testMatchに先ほど作成したtests/auth.setup.tsを指定する。
import type { PlaywrightTestConfig } from "@playwright/test";
import { Env } from "./environments/env";
const config: PlaywrightTestConfig = {
...(省略)
projects: [
// Setup project
{
name: "setup",
use: {
...devices["Desktop Chrome"],
...devices["Desktop Firefox"],
...devices["Desktop Safari"],
storageState: undefined,
},
testMatch: "/tests/auth.setup.ts",
},
...(省略)
};
export default config;
保存したログイン情報を使用して実行するプロジェクトにstorageStateとdependenciesを追加する
dependenciesに先ほど追加したsetupを指定、storageStateに環境変数で定義したログイン情報を保存するファイルパスを指定する。
import type { PlaywrightTestConfig } from "@playwright/test";
import { Env } from "./environments/env";
const config: PlaywrightTestConfig = {
...(省略)
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
storageState: Env.Users.Default.StateFilePath, // 環境変数で定義したログイン情報を保存するファイルパスを指定する
},
dependencies: ["setup"], // setupプロジェクトを指定する
testMatch: [
"/tests/*.spec.ts",
],
},
...(省略)
};
export default config;
これで--project=chromiumのテストはEnv.Users.Default.StateFilePathに保存されたログイン情報でログイン済みの状態で実行される🎉
dependencies
このプロジェクトのテストを実行する前に実行する必要があるプロジェクトのリスト。依存関係は、すべてのアクションがテストの形式になるようにグローバルセットアップのアクションを設定するのに便利です。no-deps 引数を渡すと、依存関係は無視され、指定されていないものとして動作します。
依存関係を使用すると、グローバルセットアップでトレースやその他の成果物を作成したり、テストレポートでセットアップステップを確認したりできるようになります。
test.use({})使用して、各テストでログイン情報を切り替えてみる
playwright.config.tsでstorageState: Env.Users.Default.StateFilePathを指定したプロジェクトでテストを実行した場合
test.describe(`ライトモードのユーザーでテストする`, () => {
// `Env.Users.Default.StateFilePath`に保存されたログイン情報のユーザーとしてテストを実行する
test("ライトモードで表示される", async ({ page }, testInfo) => {
...(省略)
});
});
test.describe(`ダークモードのユーザーでテストする`, () => {
// storageStateを`Env.Users.DarkMode.StateFilePath`で上書きしている
test.use({ storageState: Env.Users.DarkMode.StateFilePath });
test("ダークモードで表示される", async ({ page }, testInfo) => {
...(省略)
});
});