e2e-testing
End-to-end testing patterns with Playwright for full-stack Python/React applications. Use when writing E2E tests for complete user workflows (login, CRUD, navigation), critical path regression tests, or cross-browser validation. Covers test structure, page object model, selector strategy (data-testid > role > label), wait strategies, auth state reuse, test data management, and CI integration. Does NOT cover unit tests or component tests (use pytest-patterns or react-testing-patterns).
What this skill does
# E2E Testing
## When to Use
Activate this skill when:
- Writing E2E tests for complete user workflows (login, CRUD operations, multi-page flows)
- Creating critical path regression tests that validate the full stack
- Testing cross-browser compatibility (Chromium, Firefox, WebKit)
- Validating authentication flows end-to-end
- Testing file upload/download workflows
- Writing smoke tests for deployment verification
Do NOT use this skill for:
- React component unit tests (use `react-testing-patterns`)
- Python backend unit/integration tests (use `pytest-patterns`)
- TDD workflow enforcement (use `tdd-workflow`)
- API contract testing without a browser (use `pytest-patterns` with httpx)
## Instructions
### Test Structure
```
e2e/
├── playwright.config.ts # Global Playwright configuration
├── fixtures/
│ ├── auth.fixture.ts # Authentication state setup
│ └── test-data.fixture.ts # Test data creation/cleanup
├── pages/
│ ├── base.page.ts # Base page object with shared methods
│ ├── login.page.ts # Login page object
│ ├── users.page.ts # Users list page object
│ └── user-detail.page.ts # User detail page object
├── tests/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── logout.spec.ts
│ ├── users/
│ │ ├── create-user.spec.ts
│ │ ├── edit-user.spec.ts
│ │ └── list-users.spec.ts
│ └── smoke/
│ └── critical-paths.spec.ts
└── utils/
├── api-helpers.ts # Direct API calls for test setup
└── test-constants.ts # Shared constants
```
**Naming conventions:**
- Test files: `<feature>.spec.ts`
- Page objects: `<page-name>.page.ts`
- Fixtures: `<concern>.fixture.ts`
- Test names: human-readable sentences describing the user action and expected outcome
### Page Object Model
Every page gets a page object class that encapsulates selectors and actions. Tests never interact with selectors directly.
**Base page object:**
```typescript
// e2e/pages/base.page.ts
import { type Page, type Locator } from "@playwright/test";
export abstract class BasePage {
constructor(protected readonly page: Page) {}
/** Navigate to the page's URL. */
abstract goto(): Promise<void>;
/** Wait for the page to be fully loaded. */
async waitForLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
}
/** Get a toast/notification message. */
get toast(): Locator {
return this.page.getByRole("alert");
}
/** Get the page heading. */
get heading(): Locator {
return this.page.getByRole("heading", { level: 1 });
}
}
```
**Concrete page object:**
```typescript
// e2e/pages/users.page.ts
import { type Page, type Locator } from "@playwright/test";
import { BasePage } from "./base.page";
export class UsersPage extends BasePage {
// ─── Locators ─────────────────────────────────────────
readonly createButton: Locator;
readonly searchInput: Locator;
readonly userTable: Locator;
constructor(page: Page) {
super(page);
this.createButton = page.getByTestId("create-user-btn");
this.searchInput = page.getByRole("searchbox", { name: /search users/i });
this.userTable = page.getByRole("table");
}
// ─── Actions ──────────────────────────────────────────
async goto(): Promise<void> {
await this.page.goto("/users");
await this.waitForLoad();
}
async searchFor(query: string): Promise<void> {
await this.searchInput.fill(query);
// Wait for search results to update (debounced)
await this.page.waitForResponse("**/api/v1/users?*");
}
async clickCreateUser(): Promise<void> {
await this.createButton.click();
}
async getUserRow(email: string): Promise<Locator> {
return this.userTable.getByRole("row").filter({ hasText: email });
}
async getUserCount(): Promise<number> {
// Subtract 1 for header row
return (await this.userTable.getByRole("row").count()) - 1;
}
}
```
**Rules for page objects:**
- One page object per page or major UI section
- Locators are public readonly properties
- Actions are async methods
- Page objects never contain assertions -- tests assert
- Page objects handle waits internally after actions
### Selector Strategy
**Priority order (highest to lowest):**
| Priority | Selector | Example | When to Use |
|----------|----------|---------|-------------|
| 1 | `data-testid` | `getByTestId("submit-btn")` | Interactive elements, dynamic content |
| 2 | Role | `getByRole("button", { name: /save/i })` | Buttons, links, headings, inputs |
| 3 | Label | `getByLabel("Email")` | Form inputs with labels |
| 4 | Placeholder | `getByPlaceholder("Search...")` | Search inputs |
| 5 | Text | `getByText("Welcome back")` | Static text content |
**NEVER use:**
- CSS selectors (`.class-name`, `#id`) -- brittle, break on styling changes
- XPath (`//div[@class="foo"]`) -- unreadable, extremely brittle
- DOM structure selectors (`div > span:nth-child(2)`) -- break on layout changes
**Adding data-testid attributes:**
```tsx
// In React components -- add data-testid to interactive elements
<button data-testid="create-user-btn" onClick={handleCreate}>
Create User
</button>
// Convention: kebab-case, descriptive
// Pattern: <action>-<entity>-<element-type>
// Examples: create-user-btn, user-email-input, delete-confirm-dialog
```
### Wait Strategies
**NEVER use hardcoded waits:**
```typescript
// BAD: Hardcoded wait -- flaky, slow
await page.waitForTimeout(3000);
// BAD: Sleep
await new Promise((resolve) => setTimeout(resolve, 2000));
```
**Use explicit wait conditions:**
```typescript
// GOOD: Wait for a specific element to appear
await page.getByRole("heading", { name: "Dashboard" }).waitFor();
// GOOD: Wait for navigation
await page.waitForURL("/dashboard");
// GOOD: Wait for API response
await page.waitForResponse(
(response) =>
response.url().includes("/api/v1/users") && response.status() === 200,
);
// GOOD: Wait for network to settle
await page.waitForLoadState("networkidle");
// GOOD: Wait for element state
await page.getByTestId("submit-btn").waitFor({ state: "visible" });
await page.getByTestId("loading-spinner").waitFor({ state: "hidden" });
```
**Auto-waiting:** Playwright auto-waits for elements to be actionable before clicking, filling, etc. Explicit waits are needed only for assertions or complex state transitions.
### Auth State Reuse
Avoid logging in before every test. Save auth state and reuse it.
**Setup auth state once:**
```typescript
// e2e/fixtures/auth.fixture.ts
import { test as base } from "@playwright/test";
import path from "path";
const AUTH_STATE_PATH = path.resolve("e2e/.auth/user.json");
export const setup = base.extend({});
setup("authenticate", async ({ page }) => {
// Perform real login
await page.goto("/login");
await page.getByLabel("Email").fill("[email protected]");
await page.getByLabel("Password").fill("TestPassword123!");
await page.getByRole("button", { name: /sign in/i }).click();
// Wait for auth to complete
await page.waitForURL("/dashboard");
// Save signed-in state
await page.context().storageState({ path: AUTH_STATE_PATH });
});
```
**Reuse in tests:**
```typescript
// playwright.config.ts
export default defineConfig({
projects: [
// Setup project runs first and saves auth state
{ name: "setup", testDir: "./e2e/fixtures", testMatch: "auth.fixture.ts" },
{
name: "chromium",
use: {
storageState: "e2e/.auth/user.json", // Reuse auth state
},
dependencies: ["setup"],
},
],
});
```
### Test Data Management
**Principles:**
- Tests create their own data (never depend on pre-existing data)
- Tests clean up after themselves (or use API to reset)
- Use API calls for setup, not UI interactions (faster, more reliable)
**API helpers for test data:**
```typescript
// e2e/utils/api-helpers.ts
import { type APIRequestContext } from "@playwright/test";
export class TestDataAPI {
constructor(private request: APIRequesRelated in Design
contribute
IncludedLocal-only OSS contribution command center. Auto-refreshes the user's in-flight PR and issue state on invoke so conversations start with full context — no need to brief Claude on what's in flight. Helps the user find issues to contribute to on GitHub, builds per-repo dossiers of what each upstream expects (CLA, DCO, branch convention, AI policy, draft-first, review bots, issue templates), runs deterministic gates before any external action so AI-assisted contributions don't reach maintainers as slop. State is markdown-only: candidate files at ~/.contribute-system/candidates/, repo dossiers at ~/.contribute-system/research/, append-only event log at ~/.contribute-system/log.jsonl. No database, no cloud calls. Use when the user asks about their PRs / issues / contributions, wants to find new work to take on, claim an issue, build/refresh a repo's dossier, or draft a Design Issue or PR. Trigger with "/contribute", "what's my PR status", "find a contribution", "claim issue X", "draft a Design Issue for Y", "refresh dossier for Z".
architectural-analysis
IncludedUser-triggered deep architectural analysis of a codebase or scoped subtree across eight modes — information architecture, data flow, integration points, UI surfaces, interaction patterns, data model, control flow, and failure modes. This skill should be used when the user asks to "diagram this codebase," "map the architecture," "show the data flow," "give me an ERD," "trace control flow," "find the integration points," "verify the layout pattern," "audit the UX architecture," or any similar request whose primary deliverable is mermaid diagrams plus cited reports under docs/architecture/. Dispatches haiku/sonnet sub-agents in parallel for per-mode exploration, then verifies every citation mechanically before any node lands in a diagram. Not for one-off prose explanations of code (use code-explanation) or for high-level system design from scratch (use system-design).
mcp
IncludedModel Context Protocol (MCP) server development and tool management. Languages: Python, TypeScript. Capabilities: build MCP servers, integrate external APIs, discover/execute MCP tools, manage multi-server configs, design agent-centric tools. Actions: create, build, integrate, discover, execute, configure MCP servers/tools. Keywords: MCP, Model Context Protocol, MCP server, MCP tool, stdio transport, SSE transport, tool discovery, resource provider, prompt template, external API integration, Gemini CLI MCP, Claude MCP, agent tools, tool execution, server config. Use when: building MCP servers, integrating external APIs as MCP tools, discovering available MCP tools, executing MCP capabilities, configuring multi-server setups, designing tools for AI agents.
react-native-skia
IncludedDesign, build, debug, and optimise high-polish animated graphics in React Native or Expo using @shopify/react-native-skia, Reanimated, and Gesture Handler. Use when the user wants canvas-driven UI, shaders, paths, rich text, image filters, sprite fields, Skottie, video frames, snapshots, web CanvasKit setup, or performance tuning for custom motion-heavy elements such as loaders, hero art, cards, charts, progress indicators, particle systems, or gesture-driven surfaces. Also use when the user asks for fluid, glow, glass, blob, parallax, 60fps/120fps, or GPU-friendly animated effects in React Native, even if they do not explicitly say "Skia". Do not use for ordinary form/layout work with standard views.
plaid
IncludedProduct Led AI Development — guides founders from idea to launched product. Six capabilities: Idea (discover a product idea), Validate (pressure-test the idea against fatal flaws, problem reality, competition, and 2-week MVP feasibility), Plan (vision intake + document generation), Design (translate image references into a design.md spec), Launch (go-to-market strategy), and Build (roadmap execution). Use when someone says "PLAID", "plaid idea", "help me find an idea", "product idea", "idea from my business", "idea from my expertise", "plaid validate", "validate my idea", "pressure-test", "is this idea good", "find fatal flaws", "validate the problem", "plan a product", "define my vision", "generate a PRD", "product strategy", "plaid design", "design from image", "translate image to design", "create design.md", "extract design tokens", "plaid launch", "go-to-market", "launch plan", "GTM strategy", "launch playbook", "plaid build", "build the app", "start building", or "execute the roadmap".
nextjs-framer-motion-animations
IncludedAdds production-safe Motion for React or Framer Motion animations to Next.js apps, including reveal, hover and tap micro-interactions, whileInView, stagger, AnimatePresence, layout and layoutId transitions, reorder, scroll-linked UI, and lightweight route-content transitions. Use when the user asks to add, refactor, or debug Motion or Framer Motion in App Router or Pages Router codebases, especially around server/client boundaries, reduced motion, LazyMotion, bundle size, hydration, or route transitions. Avoid for GSAP-style timelines, WebGL or 3D scenes, heavy scroll storytelling, or CSS-only effects unless Motion is explicitly requested.