pinpoint-e2e

star 2

E2E testing guide for PinPoint (Playwright, Isolation, Mailpit, Supabase). Use when writing, debugging, or fixing E2E tests to ensure worker isolation and stability.

timothyfroehlich By timothyfroehlich schedule Updated 5/26/2026

name: pinpoint-e2e description: E2E testing guide for PinPoint (Playwright, Isolation, Mailpit, Supabase). Use when writing, debugging, or fixing E2E tests to ensure worker isolation and stability.

PinPoint E2E Testing Skill

This skill guides you through the E2E testing infrastructure of PinPoint.

Before Writing an E2E Test (30-Second Pre-Flight)

Run this checklist BEFORE typing test("...", async ({ page }). The 2026-05 audit found that ~3/4 of new E2E specs that get filed are misallocated and could land at a cheaper, faster, more reliable layer.

  1. What bug class does this catch? See pinpoint-testing skill § "Bug Classes & Cheapest Catching Layer". Class A/B/C/E/G/H/I should not be E2E — integration or RTL unit is the right home.
  2. Does an integration or unit test already cover this feature? Run rg -l "your-keyword" src/test/ src/ first (substitute the actual feature keyword). The audit found that agents create new specs because they can't see the existing tests — most of the time you should be extending an existing file, not creating a new one. See pinpoint-testing skill § "Where Existing Coverage Lives".
  3. Class-J self-check (AGENTS.md §2.1 "Test What We Own"): Does this spec hit any URL outside localhost, 127.0.0.1, or our owned local stack (Mailpit, PGlite, local Supabase)? If yes → STOP. Mock the SDK at the boundary in src/lib/<service>/client.test.ts. Live Discord, real OAuth provider redirects, vendor email-template parsing are violations.
  4. Would the assertion be the same if I called the Server Action directly with PGlite seeded data? If yes → integration test, not E2E. The browser overhead buys nothing.
  5. Is this a genuine multi-step user journey that spans two or more page renders (e.g. login → mutate → verify across pages)? If no → almost certainly not E2E.

If all five say "E2E is the right layer", write it. Otherwise, the cheapest layer that catches the bug class wins.

Quick Start

  • Run Smoke Tests: pnpm run smoke (Fast, critical paths)
  • Run Full Suite: pnpm run e2e:full (Comprehensive — CI only, don't run locally unless asked)
  • Debug Mode: pnpm exec playwright test e2e/path/to/test.spec.ts --debug

Which Tests to Run (Decision Tree)

  1. Changed pure logic/utils?pnpm run check (unit tests, ~12s)
  2. Changed a single E2E-relevant file?pnpm exec playwright test e2e/path/to/file.spec.ts --project=chromium (~15-30s)
  3. Changed UI components/forms?pnpm run smoke (~60s)
  4. Changed auth/permissions/middleware?pnpm run smoke + targeted full specs
  5. Changed DB schema/migrations?pnpm run preflight (full suite)
  6. NEVER run e2e:full locally unless explicitly asked — that's what CI is for

Key rules for agents:

  • Always use --project=chromium for targeted runs (skip Mobile Chrome unless testing responsive)
  • Use --headed for debugging visual issues
  • pnpm run check catches 90% of issues — E2E is for integration verification, not iteration
  • If a test is flaky locally, report it — don't retry in a loop

The Golden Rule: Worker Isolation

PinPoint E2E tests run in parallel against a shared database.

YOU MUST PREVENT CROSSTALK.

  1. Unique Data: Never assume the DB is empty. Always create your own unique data.
  2. Unique Users: Do not share admin@test.com across parallel tests if those tests modify global state (e.g., settings, notifications).
  3. Unique Machines: Create a fresh machine for your test.
  4. Unique Titles: Use getTestIssueTitle("My Title") to prefix issues with [w0_xyz].

Common Helpers

  • Select Reset Assertions: Use assertSelectAtPlaceholder(trigger, placeholderText) for placeholder state, or assertSelectValue(trigger, expectedLabel) for default value state (e.g. await assertSelectValue(page.getByTestId("select-id"), "Minor")).

References

Debugging Checklist

If a test fails in CI or parallel mode:

  1. Crosstalk?: Is it seeing data from another worker? (Check screenshots for other prefixes).
    • Fix: Use getTestPrefix() filtering and unique resources.
  2. Session Lost?: Redirecting to /report/success or /login unexpectedly?
    • Fix: Ensure x-skip-autologin is NOT interfering. Add test.use({ storageState: STORAGE_STATE.<role> }) to the describe block, or use loginAs for mid-test role switches. Check test.describe.serial if tests share a user.
  3. Timeout?: Waiting for a toast or email?
    • Fix: Use waitForLoadState("networkidle") before assertions. Increase timeouts for emails.
  4. Mobile layout different?: Nav links not visible on mobile?
    • Fix: AppHeader is unified — same data-testid="app-header" on all viewports. Nav links hide below md:, BottomTabBar handles mobile navigation. Use testInfo.project.name.includes("Mobile") only when testing layout-specific behavior (e.g., checking BottomTabBar visibility).

Authentication Strategy

Decision tree for new tests:

Test type Auth approach
Tests one role throughout test.use({ storageState: STORAGE_STATE.<role> })
Switches roles mid-test loginAs(page, testInfo, { email, password })
Tests login/signup/password reset No auth — start unauthenticated
Tests public routes No auth — omit test.use()
Dynamic user (created via createTestUser) loginAs after creating the user

Available roles:

import { STORAGE_STATE } from "../support/auth-state"; // adjust path to e2e root

// STORAGE_STATE.admin      → admin@test.com
// STORAGE_STATE.member     → member@test.com
// STORAGE_STATE.technician → technician@test.com

No auth needed for unauthenticated tests — simply omit test.use().

Creating a New Test

  1. Scaffold (single-role — preferred):

    import { test, expect } from "@playwright/test";
    import { STORAGE_STATE } from "../support/auth-state";
    import { getTestIssueTitle } from "../support/test-isolation";
    
    test.describe("My Feature", () => {
      test.use({ storageState: STORAGE_STATE.member });
    
      test("my feature works", async ({ page }) => {
        const title = getTestIssueTitle("Feature Test");
        await page.goto("/dashboard");
        // ...
      });
    });
    
  2. Scaffold (multi-role or auth flow — use loginAs):

    import { test, expect } from "@playwright/test";
    import { loginAs } from "../support/actions";
    
    test("role-switch works", async ({ page }, testInfo) => {
      await loginAs(page, testInfo); // logs in as member
      // ... do member actions
    });
    
  3. Isolate: If modifying global state, create a temp user/machine in beforeAll.

  4. Cleanup: Delete created resources in afterAll.

Install via CLI
npx skills add https://github.com/timothyfroehlich/PinPoint --skill pinpoint-e2e
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
timothyfroehlich
timothyfroehlich Explore all skills →