PinPoint E2E Testing Skill
This skill guides you through the E2E testing infrastructure of PinPoint.
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)
- Changed pure logic/utils? →
pnpm run check (unit tests, ~12s)
- Changed a single E2E-relevant file? →
pnpm exec playwright test e2e/path/to/file.spec.ts --project=chromium (~15-30s)
- Changed UI components/forms? →
pnpm run smoke (~60s)
- Changed auth/permissions/middleware? →
pnpm run smoke + targeted full specs
- Changed DB schema/migrations? →
pnpm run preflight (full suite)
- 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.
- Unique Data: Never assume the DB is empty. Always create your own unique data.
- Unique Users: Do not share
admin@test.com across parallel tests if those tests modify global state (e.g., settings, notifications).
- Unique Machines: Create a fresh machine for your test.
- Unique Titles: Use
getTestIssueTitle("My Title") to prefix issues with [w0_xyz].
References
- Best Practices: See references/e2e-best-practices.md for structure and anti-patterns.
- Isolation Patterns: See references/isolation-patterns.md for how to use
test-isolation.ts and supabase-admin.ts.
- Helpers: See references/common-helpers.md for
actions.ts, page-helpers.ts, and mailpit.ts.
Debugging Checklist
If a test fails in CI or parallel mode:
- Crosstalk?: Is it seeing data from another worker? (Check screenshots for other prefixes).
- Fix: Use
getTestPrefix() filtering and unique resources.
- 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.
- Timeout?: Waiting for a toast or email?
- Fix: Use
waitForLoadState("networkidle") before assertions. Increase timeouts for emails.
- 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
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");
// ...
});
});
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
});
Isolate: If modifying global state, create a temp user/machine in beforeAll.
Cleanup: Delete created resources in afterAll.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: pinpoint-e2e3description: E2E testing guide for PinPoint (Playwright, Isolation, Mailpit, Supabase). Use when writing, debugging, or fixing E2E tests to ensure worker isolation and stability. Use when this capability is needed.4---56# PinPoint E2E Testing Skill78This skill guides you through the E2E testing infrastructure of PinPoint.910## Quick Start1112- **Run Smoke Tests**: `pnpm run smoke` (Fast, critical paths)13- **Run Full Suite**: `pnpm run e2e:full` (Comprehensive — CI only, don't run locally unless asked)14- **Debug Mode**: `pnpm exec playwright test e2e/path/to/test.spec.ts --debug`1516## Which Tests to Run (Decision Tree)17181. **Changed pure logic/utils?** → `pnpm run check` (unit tests, ~12s)192. **Changed a single E2E-relevant file?** → `pnpm exec playwright test e2e/path/to/file.spec.ts --project=chromium` (~15-30s)203. **Changed UI components/forms?** → `pnpm run smoke` (~60s)214. **Changed auth/permissions/middleware?** → `pnpm run smoke` + targeted full specs225. **Changed DB schema/migrations?** → `pnpm run preflight` (full suite)236. **NEVER** run `e2e:full` locally unless explicitly asked — that's what CI is for2425**Key rules for agents:**2627- Always use `--project=chromium` for targeted runs (skip Mobile Chrome unless testing responsive)28- Use `--headed` for debugging visual issues29- `pnpm run check` catches 90% of issues — E2E is for integration verification, not iteration30- If a test is flaky locally, report it — don't retry in a loop3132## The Golden Rule: Worker Isolation3334PinPoint E2E tests run in parallel against a **shared database**.3536**YOU MUST PREVENT CROSSTALK.**37381. **Unique Data**: Never assume the DB is empty. Always create your own unique data.392. **Unique Users**: Do not share `admin@test.com` across parallel tests if those tests modify global state (e.g., settings, notifications).403. **Unique Machines**: Create a fresh machine for your test.414. **Unique Titles**: Use `getTestIssueTitle("My Title")` to prefix issues with `[w0_xyz]`.4243## References4445- **Best Practices**: See [references/e2e-best-practices.md](references/e2e-best-practices.md) for structure and anti-patterns.46- **Isolation Patterns**: See [references/isolation-patterns.md](references/isolation-patterns.md) for how to use `test-isolation.ts` and `supabase-admin.ts`.47- **Helpers**: See [references/common-helpers.md](references/common-helpers.md) for `actions.ts`, `page-helpers.ts`, and `mailpit.ts`.4849## Debugging Checklist5051If a test fails in CI or parallel mode:52531. **Crosstalk?**: Is it seeing data from another worker? (Check screenshots for other prefixes).54 - _Fix_: Use `getTestPrefix()` filtering and unique resources.552. **Session Lost?**: Redirecting to `/report/success` or `/login` unexpectedly?56 - _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.573. **Timeout?**: Waiting for a toast or email?58 - _Fix_: Use `waitForLoadState("networkidle")` before assertions. Increase timeouts for emails.594. **Mobile layout different?**: Nav links not visible on mobile?60 - _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).6162## Authentication Strategy6364**Decision tree for new tests:**6566| Test type | Auth approach |67| :------------------------------------------ | :------------------------------------------------- |68| Tests one role throughout | `test.use({ storageState: STORAGE_STATE.<role> })` |69| Switches roles mid-test | `loginAs(page, testInfo, { email, password })` |70| Tests login/signup/password reset | No auth — start unauthenticated |71| Tests public routes | No auth — omit `test.use()` |72| Dynamic user (created via `createTestUser`) | `loginAs` after creating the user |7374**Available roles:**7576```typescript77import { STORAGE_STATE } from "../support/auth-state"; // adjust path to e2e root7879// STORAGE_STATE.admin → admin@test.com80// STORAGE_STATE.member → member@test.com81// STORAGE_STATE.technician → technician@test.com82```8384No auth needed for unauthenticated tests — simply omit `test.use()`.8586## Creating a New Test87881. **Scaffold** (single-role — preferred):8990 ```typescript91 import { test, expect } from "@playwright/test";92 import { STORAGE_STATE } from "../support/auth-state";93 import { getTestIssueTitle } from "../support/test-isolation";9495 test.describe("My Feature", () => {96 test.use({ storageState: STORAGE_STATE.member });9798 test("my feature works", async ({ page }) => {99 const title = getTestIssueTitle("Feature Test");100 await page.goto("/dashboard");101 // ...102 });103 });104 ```1051062. **Scaffold** (multi-role or auth flow — use loginAs):107108 ```typescript109 import { test, expect } from "@playwright/test";110 import { loginAs } from "../support/actions";111112 test("role-switch works", async ({ page }, testInfo) => {113 await loginAs(page, testInfo); // logs in as member114 // ... do member actions115 });116 ```1171183. **Isolate**: If modifying global state, create a temp user/machine in `beforeAll`.1194. **Cleanup**: Delete created resources in `afterAll`.120121---122> Converted and distributed by [TomeVault](https://tomevault.io/claim/timothyfroehlich) — claim your Tome and manage your conversions.123<!-- tomevault:4.0:skill_md:2026-04-13 -->