一键导入
e2e-setup
Set up Playwright E2E testing framework for any project — auto-detects stack, auth, routes, and selectors
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Set up Playwright E2E testing framework for any project — auto-detects stack, auth, routes, and selectors
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Expert guidance for ffuf web fuzzing during penetration testing, including authenticated fuzzing with raw requests, auto-calibration, and result analysis
Catch up on uncommitted work — read all uncommitted git changes (staged, unstaged, untracked) into the conversation to establish context. Use when resuming work or when the user runs /catchup.
Bring a JavaScript repo up to the standard Code Quality Playbook baseline (Prettier, ESLint ratchet, husky + lint-staged, CI lint gate, optional JSDoc typecheck) idempotently — present pieces are skipped, missing ones are added — then optionally migrate the repo from npm to pnpm (lockfile import, packageManager pin, build-script allow-list, Firebase functions-framework fix, npm cleanup). Manual, global. Use only when the user runs /code-quality.
Create git commits for staged and unstaged changes using the emoji commit-message format, with optional push. Use when the user wants to commit or check in changes, or runs /commit.
Execute one complete unit of work end-to-end through tight feedback loops — understand it, plan it, build it, validate it, commit it — never advancing a phase until its feedback signal is green. Manual and global: runs only when the user invokes /do-work or another skill explicitly hands it a scoped unit of work. Use to "do the work", "execute this task/plan/issue", "build and validate and commit", or as the execution engine other planning skills delegate to.
Generate a structured development epic through an interactive, phase-by-phase approval workflow that breaks a large piece of work into tasks. Use to create an epic, break down a large feature, or when the user runs /epic.
| name | e2e-setup |
| description | Set up Playwright E2E testing framework for any project — auto-detects stack, auth, routes, and selectors |
| allowed-tools | Read, Glob, Grep, Bash, Write, Edit, Agent, AskUserQuestion |
You are setting up a production-grade Playwright E2E testing framework for the current project. You must auto-detect as much as possible and ask the user only what you can't figure out.
Read package.json (and package-lock.json, yarn.lock, pnpm-lock.yaml if they exist) to determine:
tsconfig.json, .ts/.tsx files)"type": "module") or CJSCheck these sources in order to find the dev server port:
vite.config.* → server.portnext.config.* → check for custom portangular.json → serve.options.portpackage.json scripts → look for --port flags in dev/start scriptsRecord this as DEV_PORT. The test port will be DEV_PORT + 26 (e.g., 5173→5199, 3000→3026) to avoid conflicts with a running dev server. This is configurable via TEST_PORT env var.
Read the dev or start script from package.json. This is what Playwright's webServer block will run, with the port overridden to the test port.
Search for imports and usage of:
firebase/auth, connectAuthEmulator@supabase/supabase-js, supabase.auth@auth0/auth0-react, @auth0/nextjs-auth0@clerk/clerk-react, @clerk/nextjsnext-auth, @auth/corepassport, passport-localjsonwebtokenRecord which provider and which auth services are initialized (email/password, Google SSO, magic link, etc.).
Search for:
firebase/firestore, connectFirestoreEmulatorsupabase.from()@prisma/clientmongoose, mongodbpg, postgresbetter-sqlite3, sql.jsdrizzle-ormCheck for:
firebase.json → emulator config (which emulators, which ports).env.local, .env.development → local service URLsdocker-compose.yml → local database/service containerssupabase/config.toml → Supabase local configRecord which emulators/local services are available and their ports.
Find the router configuration:
createBrowserRouter, <Route, <Routes, a routes config fileapp/ or pages/ directory structuresrc/routes/ directory structurepages/ directory structureRouterModule, route config filescreateRouter, route configCategorize discovered routes into:
Find the login and registration components by searching for:
login, signin, sign-in, auth in component/page directoriesregister, signup, sign-up in component/page directoriesRead those files and extract the actual selectors:
placeholder, name, id, data-testid, aria-label attributestype="submit", data-testidSearch for role checks in the codebase:
Record the role hierarchy (e.g., user → admin → owner).
After detection, present a summary of what you found and ask:
"Do you have a staging or dev deployment, or just local + prod?"
"What is your production URL?" (if not detectable from config files, hosting config, or package.json homepage field)
Present the flows you auto-detected from route groups and components:
Confirm your detection results — show what you found for framework, auth, database, routes, selectors, and proposed flows. Let the user correct anything wrong.
{pkg_manager} install -D @playwright/test
npx playwright install chromium
Create this structure (adapt filenames to the project's language — .ts if TypeScript, .js if JavaScript):
tests/
e2e/
flows/
helpers/
auth.{ext} # Building blocks: loginUser, signupUser, logoutUser, waitForAuth
concurrency.{ext} # assertAllFlowsSucceeded utility
browse.flow.{ext} # Full journey: public page navigation with assertions at every step
registration.flow.{ext} # Full journey: signup through onboarding
dashboard.flow.{ext} # Full journey: login → primary app usage → logout
# (additional flows auto-detected from Phase 1 route/component scan)
single/
browse.spec.{ext} # Imports browse flow, runs once
registration.spec.{ext} # Imports registration flow, runs once
dashboard.spec.{ext} # Imports dashboard flow, runs once
auth/
user-login.spec.{ext}
invalid-creds.spec.{ext}
[admin-login.spec.{ext}] # Only if admin role detected
concurrent/
multi-user.spec.{ext} # Imports flows, runs N in parallel with mixed traffic
smoke/
health.spec.{ext}
helpers/
env.{ext} # Environment/target config
setup.{ext} # Test user seeding (if emulators/local DB available)
fixtures/
test-users.json
playwright.config.{ext}
Key structural principle: Flow definitions (flows/*.flow.{ext}) are plain exported functions, not test files. They take a page (and optional params) and walk through a complete user journey with assertions at every step. Test files in single/ and concurrent/ import these flows and run them in different execution contexts. Write the walkthrough once, run it alone to verify correctness, then run N of them to verify concurrency.
tests/e2e/helpers/env.{ext})Create an env config that:
TEST_TARGET env var — defaults to localTEST_PORT env var, falling back to the computed test portCONCURRENT_USERS env var for parallel browser contexts in concurrent tests, defaulting to 5 (safe for local dev machines; scale up in CI){ baseURL, useEmulators, project, concurrentUsers }getConfig() functionOnly include targets the user confirmed.
playwright.config.{ext})testDir: tests/e2etimeout: 60000 (concurrent flows need headroom)expect.timeout: 10000'html' by default. When E2E_LOG env var is set, use multi-reporter: [['list'], ['html'], ['json', { outputFile: 'e2e-logs/<ISO-timestamp>.json' }]]. Timestamp uses new Date().toISOString().replace(/[:.]/g, '-').webServer: start the project's dev server on the test port. Set any emulator env vars when target is local. Use reuseExistingServer: false always.local — baseURL from env config, runs all testsstaging — only if user confirmed staging existsprod — baseURL from prod URL, runs all testssmoke-only — prod baseURL, testDir limited to tests/e2e/smoke/Framework-specific webServer adjustments:
npx vite --port {TEST_PORT}npx next dev -p {TEST_PORT}PORT={TEST_PORT} npx react-scripts startnpx ng serve --port {TEST_PORT}PORT={TEST_PORT} npx remix devnpx astro dev --port {TEST_PORT}npx vite dev --port {TEST_PORT}npx nuxt dev --port {TEST_PORT}Framework-specific emulator env vars:
VITE_ (e.g., VITE_USE_EMULATORS=true)NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_USE_EMULATORS=true)REACT_APP_ (e.g., REACT_APP_USE_EMULATORS=true)environment.ts or env varsBased on what was detected:
Firebase: Add conditional emulator connection in the Firebase init file if not already present. Only connect emulators that are actually configured. Use ports from firebase.json. Prefer port 8085 for Firestore if configuring fresh (8080 conflicts with other services).
Supabase: Point to local Supabase instance (default http://localhost:54321).
Docker services: Document that docker-compose up is needed before tests.
No local services: Skip this step, tests will run against live services.
Important: Minimize changes to source code. Only add the emulator/local connection conditional if it doesn't already exist.
Flows are the core abstraction of this framework. There are two layers:
tests/e2e/flows/helpers/auth.{ext})Low-level building blocks that other flows compose. These perform a single action and return — they are not full journeys. Using the actual selectors detected in Phase 1, create:
loginUser(page, email, password): Navigate to login page, fill credentials using real selectors, submit, wait for navigation away from loginsignupUser(page, { name, email, password, ...fields }): Navigate to register page, fill all fields using real selectors, submit (note any post-signup redirects like payment)logoutUser(page): Find and click logout using real selectors, wait for redirect to loginwaitForAuth(page): Wait for auth state to resolve — either dashboard content appears or auth page appearsUse the exact selectors from the actual components. Do not guess or use generic selectors. If a selector can't be determined, leave a // TODO: update selector comment.
tests/e2e/flows/*.flow.{ext})Full user journeys that take a page (and optional params) and walk through a complete path with assertions at every step. Not just "click and move on" — each step waits, then verifies the UI is correct before proceeding.
The specific flows to generate are auto-detected from Phase 1 scanning of routes and components. Do not prescribe fixed flows — detect what the project has and generate flows that match. Common patterns:
Browse flow (browse.flow.{ext}) — for projects with public pages:
Registration flow (registration.flow.{ext}) — for projects with user signup:
signupUser helper internallyDashboard/primary app flow (dashboard.flow.{ext}) — for projects with authenticated areas:
loginUser helper to authenticate → assert main app view renders → navigate to primary feature → assert feature content loads → navigate to secondary feature → assert it loads → logout → assert return to public/login pageloginUser and logoutUser helpers internallyGenerate flows based on what the project actually has. A CRUD app gets a CRUD flow. A read-only dashboard gets a dashboard flow. A content site gets a browse flow. A project with settings gets a settings flow. The key contract for every flow:
// Every flow follows this contract:
// - Takes a page and optional params
// - Walks through a complete linear journey
// - Asserts at EVERY step before proceeding
// - Throws on any assertion failure
export async function browseFlow(page) {
await page.goto('/');
await expect(page.locator('...')).toBeVisible(); // assert before moving on
await page.click('...');
await expect(page.locator('...')).toBeVisible(); // assert before moving on
// ... every step follows this pattern
}
tests/e2e/flows/helpers/concurrency.{ext})Export a utility for asserting on Promise.allSettled results — this prevents concurrent tests from silently swallowing failures:
import { expect } from '@playwright/test';
export function assertAllFlowsSucceeded(results) {
const failures = results
.map((r, i) => ({ ...r, index: i }))
.filter(r => r.status === 'rejected');
if (failures.length > 0) {
const details = failures
.map(f => ` User ${f.index}: ${f.reason?.message || f.reason}`)
.join('\n');
expect(failures, `${failures.length} flow(s) failed:\n${details}`).toHaveLength(0);
}
}
tests/fixtures/test-users.json)Create 5 test users with email/password. If the registration form has additional required fields (like displayName), include those.
If admin roles exist, note that an admin user will be seeded separately in the setup script.
tests/e2e/helpers/setup.{ext})Only create this if local emulators or a local database are available.
Create a script that:
tests/fixtures/test-users.jsonnode tests/e2e/helpers/setup.{ext}Provider-specific seeding:
firebase-admin SDK with emulator env vars@supabase/supabase-js with local service URLUser login tests (tests/e2e/auth/user-login.spec.{ext}):
E2E_USER_EMAIL / E2E_USER_PASSWORD env vars with defaultsdomcontentloaded for page waits (avoid networkidle — live content like timers/websockets prevent it)Admin login tests (tests/e2e/auth/admin-login.spec.{ext}) — only if admin role detected:
E2E_ADMIN_EMAIL / E2E_ADMIN_PASSWORD env vars with defaultsInvalid credentials tests (tests/e2e/auth/invalid-creds.spec.{ext}):
tests/e2e/smoke/health.spec.{ext})A minimal test safe to run against production — NO writes, NO account creation:
/ (home page)index.html or layout)page.on('console'))tests/e2e/single/*.spec.{ext})Create one spec file per flow definition. Each test imports the flow and runs it once in a standard Playwright page:
// tests/e2e/single/browse.spec.{ext}
import { test } from '@playwright/test';
import { browseFlow } from '../flows/browse.flow.{ext}';
test('browse flow completes successfully', async ({ page }) => {
await browseFlow(page);
});
Create a spec for every flow generated in section 3.6. These are your "does this walkthrough work" tests — run them to verify each journey is correct before using them in concurrent tests.
tests/e2e/concurrent/multi-user.spec.{ext})Create tests that import flow definitions and run them in parallel using separate browser contexts. Read CONCURRENT_USERS from the env config (default 5).
Homogeneous concurrency — N users running the same flow:
import { test } from '@playwright/test';
import { dashboardFlow } from '../flows/dashboard.flow.{ext}';
import { assertAllFlowsSucceeded } from '../flows/helpers/concurrency.{ext}';
import { getConfig } from '../helpers/env.{ext}';
import testUsers from '../../fixtures/test-users.json';
const { concurrentUsers } = getConfig();
test('multiple users access dashboard simultaneously', async ({ browser }) => {
const users = testUsers.slice(0, concurrentUsers);
const results = await Promise.allSettled(
users.map(async (user) => {
const context = await browser.newContext();
const page = await context.newPage();
try {
await dashboardFlow(page, user);
} finally {
await context.close();
}
})
);
assertAllFlowsSucceeded(results);
});
Mixed traffic simulation — different flows running concurrently (the realistic scenario):
test('mixed traffic simulation', async ({ browser }) => {
const newPage = async () => {
const ctx = await browser.newContext();
return { page: await ctx.newPage(), ctx };
};
const tasks = [
...Array(Math.ceil(concurrentUsers * 0.5)).fill(null).map(async () => {
const { page, ctx } = await newPage();
try { await browseFlow(page); } finally { await ctx.close(); }
}),
...testUsers.slice(0, Math.ceil(concurrentUsers * 0.3)).map(async (user) => {
const { page, ctx } = await newPage();
try { await dashboardFlow(page, user); } finally { await ctx.close(); }
}),
...testUsers.slice(0, Math.ceil(concurrentUsers * 0.2)).map(async (user) => {
const { page, ctx } = await newPage();
try { await registrationFlow(page, user); } finally { await ctx.close(); }
}),
];
const results = await Promise.allSettled(tasks);
assertAllFlowsSucceeded(results);
});
Key details:
assertAllFlowsSucceeded ensures no silent failures from allSettledCONCURRENT_USERS env var in CI.Add scripts using the detected package manager and framework. Template:
{
"pretest:e2e": "lsof -ti:${TEST_PORT:-{test_port}} | xargs kill -9 2>/dev/null || true",
"test:e2e": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test --project=local{emulator_wrapper_end}",
"test:e2e:quick": "npx playwright test tests/e2e/smoke/ tests/e2e/auth/invalid-creds.spec.{ext}",
"test:e2e:prod": "TEST_TARGET=prod npx playwright test --project=smoke-only",
"test:e2e:smoke": "npx playwright test tests/e2e/smoke/",
"test:e2e:auth": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test tests/e2e/auth/{emulator_wrapper_end}",
"test:e2e:flows": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test tests/e2e/single/{emulator_wrapper_end}",
"test:e2e:concurrent": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test tests/e2e/concurrent/{emulator_wrapper_end}",
"test:e2e:headed": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test --headed{emulator_wrapper_end}",
"test:e2e:debug": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && npx playwright test --headed --workers=1 --timeout=0{emulator_wrapper_end}",
"test:e2e:codegen": "npx playwright codegen http://localhost:${TEST_PORT:-{test_port}}",
"test:e2e:seed": "node tests/e2e/helpers/setup.{ext}",
"test:e2e:logs": "{emulator_wrapper_start}node tests/e2e/helpers/setup.{ext} && E2E_LOG=1 npx playwright test{emulator_wrapper_end}"
}
Where {emulator_wrapper_start/end} is:
firebase emulators:exec --only {services} '...'supabase start && ... ; supabase stop (or assume running)docker-compose up -d && ... ; docker-compose downOnly add staging scripts if user confirmed staging exists.
Design principles:
test:e2e is the default — starts everything needed, seeds data, runs all tests. No footguns.test:e2e:flows runs all single-user flow walkthroughs — verify each journey works before testing concurrency.test:e2e:quick runs only tests that don't need emulators/local services.pretest:e2e kills any process on the test port before starting.reuseExistingServer: false ensures Playwright starts its own dev server.Add these entries to .gitignore (if not already present):
# E2E test artifacts
e2e-logs/
test-results/
playwright-report/
.github/workflows/e2e.yml)Create a CI workflow that:
test-results/ and playwright-report/ as artifacts on failurenpx playwright test --list to verify all tests are discoverednpm run test:e2e:quick (or equivalent) as a first sanity checkfeat: add Playwright E2E testing framework with flow-based architecture