.agents/skills/e2e-testing/SKILL.md
# E2E Testing — Tastik Guides writing and maintaining Playwright E2E tests for Tastik. ## Philosophy - **Real dev environment**: Tests run against the actual dev deployment—no mocking Convex (WebSocket-based, can't intercept with `page.route()`). - **Dedicated test account**: A single account (`E2E_TEST_EMAIL` / `E2E_TEST_PASSWORD`) authenticates once in setup, reused across all authenticated tests. - **`e2e-` prefix**: Every test-created entity uses `uniqueName("e2e-...")` so stale artifacts
npx skillsauth add FabioFiorita/tastik .agents/skills/e2e-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guides writing and maintaining Playwright E2E tests for Tastik.
page.route()).E2E_TEST_EMAIL / E2E_TEST_PASSWORD) authenticates once in setup, reused across all authenticated tests.e2e- prefix: Every test-created entity uses uniqueName("e2e-...") so stale artifacts are identifiable and cleaned up automatically in auth.setup.ts.finally block using cleanupListByName().| Variable | Purpose |
|----------|---------|
| PLAYWRIGHT_TEST_BASE_URL | Dev URL (e.g. https://tastik-dev.xxx.workers.dev) |
| E2E_TEST_EMAIL | Test account email |
| E2E_TEST_PASSWORD | Test account password |
Set in .env.local (loaded by playwright.config.ts via dotenv).
playwright.config.ts)Three Playwright projects run sequentially:
e2e- lists, saves storageState to playwright/.auth/user.json.e2e/authenticated/. Uses saved storageState. Depends on setup.e2e/public/. Uses empty storageState. Depends on chromium-auth.Workers: 1 (serial execution to avoid conflicts on the shared test account).
e2e/
├── auth.setup.ts # Setup project: login + cleanup
├── helpers/
│ └── list-helpers.ts # Shared helpers (navigation, CRUD, locators)
├── authenticated/ # Tests requiring auth (chromium-auth project)
│ ├── app-shell.spec.ts
│ ├── item-types.spec.ts
│ ├── list-lifecycle.spec.ts
│ └── tags-and-preferences.spec.ts
└── public/ # Tests without auth (chromium-public project)
├── auth-guards.spec.ts
└── public-navigation.spec.ts
Every authenticated test follows this structure:
test("does something", async ({ page }) => {
const listName = uniqueName("e2e-descriptive-name");
try {
await createList(page, { name: listName, type: "simple" });
await openListByName(page, listName);
// ... assertions ...
} finally {
await cleanupListByName(page, listName);
}
});
uniqueName() appends timestamp + random suffix to avoid collisions.cleanupListByName() is safe to call even if the test failed mid-flow (swallows errors, checks if page is closed).e2e/helpers/list-helpers.ts)| Helper | Purpose |
|--------|---------|
| uniqueName(prefix) | Generate unique name with timestamp + random suffix |
| escapeRegExp(value) | Escape regex special chars for safe matching |
| listLinkByName(page, name) | Locator for a list link on the home page |
| itemRowByName(page, name) | Locator for an item row by [data-item-row] |
| gotoHome(page) | Navigate to /home, re-authenticate if needed |
| selectOptionByTestId(page, testId, label) | Click a data-testid select trigger, pick an option |
| createList(page, { name, type? }) | Create a list from the home page |
| openListByName(page, name) | Navigate home, click into a list, verify URL + heading |
| openListActions(page) | Click the list actions trigger |
| deleteCurrentList(page) | Delete the currently open list |
| cleanupListByName(page, name) | Safely delete a list (for finally blocks) |
| addItem(page, { name, ...fields }) | Add an item to the current list |
| deleteItem(page, itemName) | Delete an item via its actions menu |
See helpers-reference.md for full API docs.
data-testid: Primary locator strategy. Examples: add-item-button, item-name-input, create-item-submit.data-item-row: Attribute on every item row. Use itemRowByName() to find by text.data-testid^='prefix-': Dynamic test IDs include item/list IDs. Use prefix matching: [data-testid^='item-checkbox-'].data-slot: shadcn select internals. Use selectOptionByTestId() instead of manual locators.# All tests
bun test:e2e
# Single file
bunx playwright test e2e/authenticated/list-lifecycle.spec.ts
# Headed mode (see the browser)
bunx playwright test --headed
# With UI mode
bunx playwright test --ui
See writing-new-tests.md for step-by-step templates.
Quick checklist:
e2e/authenticated/ or e2e/public/ based on auth needs.test.describe() block.uniqueName("e2e-...") for all created entities.try/finally with cleanupListByName() for authenticated tests.list-helpers.ts—don't inline CRUD patterns.data-testid locators over text/role selectors.tools
Cloudflare Workers CLI for deploying, developing, and managing Workers, KV, R2, D1, Vectorize, Hyperdrive, Workers AI, Containers, Queues, Workflows, Pipelines, and Secrets Store. Load before running wrangler commands to ensure correct syntax and best practices.
development
Reviews and authors Cloudflare Workers code against production best practices. Load when writing new Workers, reviewing Worker code, configuring wrangler.jsonc, or checking for common Workers anti-patterns (streaming, floating promises, global state, secrets, bindings, observability). Biases towards retrieval from Cloudflare docs over pre-trained knowledge.
tools
Analyzes web performance using Chrome DevTools MCP. Measures Core Web Vitals (FCP, LCP, TBT, CLS, Speed Index), identifies render-blocking resources, network dependency chains, layout shifts, caching issues, and accessibility gaps. Use when asked to audit, profile, debug, or optimize page load performance, Lighthouse scores, or site speed.
development
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.