Skip to content

romradchenko/playwright-kit

Repository files navigation

playwright-kit

A focused toolkit for solving real-world Playwright E2E pain points without changing your existing workflow.

This repo is a small monorepo of production-ready utilities built on top of Playwright (not a testing framework).

Packages

  • @playwright-kit/auth - manage Playwright storageState artifacts before tests (multi-profile, ensure, CI-friendly failures).
    • Docs: packages/auth/README.md
    • Install: npm i -D @playwright-kit/auth
    • npm: https://www.npmjs.com/package/@playwright-kit/auth
    • Examples: examples/next-admin-auth, examples/vite-react-auth

Releasing

See RELEASING.md.

Auth (@playwright-kit/auth)

Infrastructure utilities for managing Playwright storageState auth artifacts before running tests.

  • Users define project-specific auth flows in playwright.auth.config.ts
  • CLI generates .auth/<profile>.json
  • CLI ensure validates and refreshes states before tests (no auto-login during test execution)

Why this package (if Playwright already has storageState)?

Playwright gives you the low-level primitives:

  • Save: context.storageState({ path })
  • Use: test.use({ storageState: "state.json" })
  • Optional: write a custom globalSetup to generate states before a run

This package provides the missing "infrastructure layer" many teams end up building themselves:

  • Multi-profile auth states (admin, user, ...) in one config file
  • Ensure workflow: validate existing states and refresh only when missing/invalid
  • Explicit pre-test step (runs before playwright test, not hidden inside fixtures)
  • Failure artifacts (trace + screenshot) in deterministic locations for CI debugging
  • Optional app startup via webServer so ensure can be self-contained in CI
  • Optional .env loading via --dotenv to keep scripts copy/paste friendly

The "Native Playwright" section below is intentional: it shows that after generation, auth states are plain Playwright storageState files - there's no runtime magic.

Install

npm i -D @playwright-kit/auth

With pnpm:

pnpm add -D @playwright-kit/auth

TL;DR (copy/paste)

  1. Add playwright.auth.config.ts (example below), then:
playwright-kit auth ensure --dotenv
playwright test
  1. In tests, either use native Playwright:
test.use({ storageState: ".auth/admin.json" });

or the wrapper:

import { authTest } from "@playwright-kit/auth";

export const test = authTest({ defaultProfile: "user" });
test.use({ auth: "admin" });

Choose:

  • Native: simplest; you reference .auth/<profile>.json directly.
  • Wrapper: cleaner multi-profile switching via test.use({ auth: "<profile>" }).

What you get

  • Config API: defineAuthConfig(...)
  • CLI: playwright-kit auth setup / playwright-kit auth ensure
  • Test ergonomics: authTest(...) (optional)

Quick start (copy/paste)

1) Add playwright.auth.config.ts

This example supports two profiles: admin and user.

import { defineAuthConfig } from "@playwright-kit/auth";

export default defineAuthConfig({
  baseURL: process.env.BASE_URL ?? "http://127.0.0.1:3000",

  // Optional: have the CLI start your app and wait until it's reachable.
  // Use this in CI (or anytime your app isn't already running).
  // If omitted, the CLI assumes your app is already running at `baseURL`.
  webServer: {
    command: "npm run dev",
    // Optional; defaults to baseURL when omitted.
    // url: "http://127.0.0.1:3000/login",
  },

  profiles: {
    admin: {
      // Recommended: set validateUrl explicitly for deterministic validation.
      // When omitted, the CLI will fall back to "/".
      validateUrl: "/admin",
      async login(page, { credentials }) {
        await page.goto("/login");
        await page.getByLabel("Email").fill(credentials.email);
        await page.getByLabel("Password").fill(credentials.password);
        await page.getByRole("button", { name: "Sign in" }).click();
      },
      async validate(page) {
        const ok = await page.getByRole("heading", { name: "Admin" }).isVisible();
        return ok ? { ok: true } : { ok: false, reason: "Admin heading not visible" };
      },
    },

    user: {
      // Recommended: set validateUrl explicitly for deterministic validation.
      // When omitted, the CLI will fall back to "/".
      validateUrl: "/me",
      async login(page, { credentials }) {
        await page.goto("/login");
        await page.getByLabel("Email").fill(credentials.email);
        await page.getByLabel("Password").fill(credentials.password);
        await page.getByRole("button", { name: "Sign in" }).click();
      },
      async validate(page) {
        const ok = await page.getByRole("heading", { name: "Me" }).isVisible();
        return ok ? { ok: true } : { ok: false, reason: "Me heading not visible" };
      },
    },
  },
});

2) Provide credentials (env convention)

Default mapping is:

  • AUTH_<PROFILE>_EMAIL
  • AUTH_<PROFILE>_PASSWORD

Where <PROFILE> is uppercased and non-alphanumerics become _ (qa-admin -> QA_ADMIN).

Examples:

  • AUTH_ADMIN_EMAIL, AUTH_ADMIN_PASSWORD
  • AUTH_USER_EMAIL, AUTH_USER_PASSWORD

3) Add scripts to package.json

This keeps tests "pure": auth refresh happens before Playwright tests run.

{
  "scripts": {
    "auth:ensure": "playwright-kit auth ensure --dotenv",
    "pretest": "npm run auth:ensure",
    "test": "playwright test"
  }
}

With pnpm:

{
  "scripts": {
    "auth:ensure": "playwright-kit auth ensure --dotenv",
    "pretest": "pnpm run auth:ensure",
    "test": "playwright test"
  }
}

Now:

npm test

CLI

playwright-kit auth setup

Generate a single profile:

playwright-kit auth setup --profile admin --dotenv

playwright-kit auth ensure

Ensure all profiles from config (default):

playwright-kit auth ensure --dotenv

Ensure a subset:

playwright-kit auth ensure --profile admin --profile user --dotenv

--dotenv / --dotenv-path

Node does not load .env automatically. If you want .env support:

playwright-kit auth ensure --dotenv

Or:

playwright-kit auth ensure --dotenv-path .env.ci

Notes:

  • .env loading is provided via the dotenv package.

Using in tests

Native Playwright

Use this if you prefer the simplest, framework-native approach: you reference the generated storageState file directly. This does not require any @playwright-kit/auth test wrapper.

import { test } from "@playwright/test";

test.use({ storageState: ".auth/admin.json" });

authTest() wrapper (recommended ergonomics)

Use this if you have multiple profiles and want clean switching via test.use({ auth: "user" }) (Playwright-native patterns), while still keeping auth refresh outside of test execution.

Create a single fixtures file and reuse it:

// tests/fixtures.ts
import { authTest } from "@playwright-kit/auth";

export const test = authTest({ defaultProfile: "user" });
export const expect = test.expect;

Usage (default profile):

import { test, expect } from "./fixtures";

test("user by default", async ({ page }) => {
  await page.goto("/me");
  await expect(page.getByTestId("whoami")).toHaveText("user");
});

Switch profile using native Playwright patterns:

import { test, expect } from "./fixtures";

test.use({ auth: "admin" });

test("admin view", async ({ page }) => {
  await page.goto("/admin");
  await expect(page.getByTestId("whoami")).toHaveText("admin");
});

The wrapper never regenerates state; if a state file is missing/invalid it fails with instructions to run playwright-kit auth ensure.

Artifacts

  • Auth states: .auth/<profile>.json
  • Failures: .auth/.failures/<profile>/<runId>/{trace.zip,screenshot.png,error.txt}
  • Locks: .auth/.locks/<profile>.lock (prevents concurrent setup/ensure from overwriting state)

.gitignore

Recommended:

.auth/

When is webServer required?

  • Required when your login() / validate() needs the app UI (via baseURL), but the app is not guaranteed to be running before playwright-kit auth ensure/setup (typical in CI).
  • Not needed if the app is already started externally (docker-compose, separate terminal/job) before running the auth CLI.
  • Recommended for CI to avoid "connection refused" and to keep ensure self-contained and deterministic.

Local dev

npm i

Contributing

See CONTRIBUTING.md.