---
description: Generate Playwright E2E tests for Tanstack Start routes, server functions, and components
---
# Playwright Test Generator Command
Automatically generate comprehensive Playwright tests for Tanstack Start routes, server functions, and components with Cloudflare Workers-specific patterns.
## Introduction
Senior QA Engineer specializing in test generation for Tanstack Start applications
This command generates ready-to-use Playwright tests that cover:
- TanStack Router route loading and navigation
- Server function calls with Cloudflare bindings
- Component interactions
- Accessibility validation
- Error handling
- Loading states
## Prerequisites
- Playwright installed (`/es-test-setup`)
- Tanstack Start project
- Route or component to test
## Command Usage
```bash
/es-test-gen [options]
```
### Arguments:
- ``: What to generate tests for
- Route path: `/users/$id`, `/dashboard`, `/blog`
- Server function: `src/lib/server-functions/createUser.ts`
- Component: `src/components/UserCard.tsx`
- `[options]`: Optional flags:
- `--with-auth`: Include authentication tests
- `--with-server-fn`: Include server function tests
- `--with-a11y`: Include accessibility tests (default: true)
- `--output `: Custom output path
### Examples:
```bash
# Generate tests for a route
/es-test-gen /users/$id
# Generate tests for server function
/es-test-gen src/lib/server-functions/createUser.ts --with-auth
# Generate tests for component
/es-test-gen src/components/UserCard.tsx --with-a11y
```
## Main Tasks
### 1. Analyze Target
Parse the target to understand what type of tests to generate.
```bash
# Determine target type
if [[ "$TARGET" == /* ]]; then
TYPE="route"
elif [[ "$TARGET" == *server-functions* ]]; then
TYPE="server-function"
elif [[ "$TARGET" == *components* ]]; then
TYPE="component"
fi
```
### 2. Generate Route Tests
For route: `/users/$id`
**Task playwright-testing-specialist(analyze route and generate tests)**:
- Identify dynamic parameters
- Detect loaders and data dependencies
- Check for authentication requirements
- Generate test cases
**Output**: `e2e/routes/users.$id.spec.ts`
```typescript
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('User Profile Page', () => {
const testUserId = '123'
test('loads user profile successfully', async ({ page }) => {
await page.goto(`/users/${testUserId}`)
// Wait for loader to complete
await page.waitForSelector('[data-testid="user-profile"]')
// Verify user data displayed
await expect(page.locator('h1')).toBeVisible()
await expect(page.locator('[data-testid="user-email"]')).toBeVisible()
})
test('shows loading state during navigation', async ({ page }) => {
await page.goto('/')
// Navigate to user profile
await page.click(`a[href="/users/${testUserId}"]`)
// Verify loading indicator
await expect(page.locator('[data-testid="loading"]')).toBeVisible()
// Wait for content to load
await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
})
test('handles non-existent user (404)', async ({ page }) => {
const response = await page.goto('/users/999999')
// Verify error state
await expect(page.locator('text=/user not found/i')).toBeVisible()
})
test('has no accessibility violations', async ({ page }) => {
await page.goto(`/users/${testUserId}`)
const accessibilityScanResults = await new AxeBuilder({ page })
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('navigates back correctly', async ({ page }) => {
await page.goto(`/users/${testUserId}`)
// Go back
await page.goBack()
// Verify we're back at previous page
await expect(page).toHaveURL('/')
})
})
```
### 3. Generate Server Function Tests
For: `src/lib/server-functions/createUser.ts`
**Output**: `e2e/server-functions/create-user.spec.ts`
```typescript
import { test, expect } from '@playwright/test'
test.describe('Create User Server Function', () => {
test('creates user successfully', async ({ page }) => {
await page.goto('/users/new')
// Fill form
await page.fill('[name="name"]', 'Test User')
await page.fill('[name="email"]', 'test@example.com')
// Submit (calls server function)
await page.click('button[type="submit"]')
// Wait for redirect
await page.waitForURL(/\/users\/\d+/)
// Verify user created
await expect(page.locator('h1')).toContainText('Test User')
})
test('validates required fields', async ({ page }) => {
await page.goto('/users/new')
// Submit empty form
await page.click('button[type="submit"]')
// Verify validation errors
await expect(page.locator('[data-testid="name-error"]'))
.toContainText(/required/i)
})
test('shows loading state during submission', async ({ page }) => {
await page.goto('/users/new')
await page.fill('[name="name"]', 'Test User')
await page.fill('[name="email"]', 'test@example.com')
// Start submission
await page.click('button[type="submit"]')
// Verify loading indicator
await expect(page.locator('button[type="submit"]')).toBeDisabled()
await expect(page.locator('[data-testid="loading"]')).toBeVisible()
})
test('handles server errors gracefully', async ({ page }) => {
await page.goto('/users/new')
// Simulate server error by using invalid data
await page.fill('[name="email"]', 'invalid-email')
await page.click('button[type="submit"]')
// Verify error message
await expect(page.locator('[data-testid="error"]')).toBeVisible()
})
test('stores data in Cloudflare D1', async ({ page, request }) => {
await page.goto('/users/new')
const testEmail = `test-${Date.now()}@example.com`
await page.fill('[name="name"]', 'D1 Test User')
await page.fill('[name="email"]', testEmail)
await page.click('button[type="submit"]')
// Wait for creation
await page.waitForURL(/\/users\/\d+/)
// Verify data persisted (reload page)
await page.reload()
await expect(page.locator('[data-testid="user-email"]'))
.toContainText(testEmail)
})
})
```
### 4. Generate Component Tests
For: `src/components/UserCard.tsx`
**Output**: `e2e/components/user-card.spec.ts`
```typescript
import { test, expect } from '@playwright/test'
test.describe('UserCard Component', () => {
test.beforeEach(async ({ page }) => {
// Navigate to component demo/storybook page
await page.goto('/components/user-card-demo')
})
test('renders user information correctly', async ({ page }) => {
await expect(page.locator('[data-testid="user-card"]')).toBeVisible()
await expect(page.locator('[data-testid="user-name"]')).toBeVisible()
await expect(page.locator('[data-testid="user-email"]')).toBeVisible()
})
test('handles click interactions', async ({ page }) => {
await page.click('[data-testid="user-card"]')
// Verify click handler triggered
await expect(page).toHaveURL(/\/users\/\d+/)
})
test('displays avatar image', async ({ page }) => {
const avatar = page.locator('[data-testid="user-avatar"]')
await expect(avatar).toBeVisible()
// Verify image loaded
await expect(avatar).toHaveJSProperty('complete', true)
})
test('has no accessibility violations', async ({ page }) => {
const accessibilityScanResults = await new AxeBuilder({ page })
.include('[data-testid="user-card"]')
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('keyboard navigation works', async ({ page }) => {
// Tab to card
await page.keyboard.press('Tab')
// Verify focus
await expect(page.locator('[data-testid="user-card"]')).toBeFocused()
// Press Enter
await page.keyboard.press('Enter')
// Verify navigation
await expect(page).toHaveURL(/\/users\/\d+/)
})
test('matches visual snapshot', async ({ page }) => {
await expect(page.locator('[data-testid="user-card"]'))
.toHaveScreenshot('user-card.png')
})
})
```
### 5. Generate Authentication Tests (--with-auth)
**Output**: `e2e/auth/protected-route.spec.ts`
```typescript
import { test, expect } from '@playwright/test'
test.describe('Protected Route - /users/$id', () => {
test('redirects to login when unauthenticated', async ({ page }) => {
await page.goto('/users/123')
// Should redirect to login
await page.waitForURL(/\/login/)
// Verify redirect query param
expect(page.url()).toContain('redirect=%2Fusers%2F123')
})
test('allows access when authenticated', async ({ page }) => {
// Login first
await page.goto('/login')
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="password"]', 'password123')
await page.click('button[type="submit"]')
// Navigate to protected route
await page.goto('/users/123')
// Should not redirect
await expect(page).toHaveURL('/users/123')
await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
})
test('redirects to original destination after login', async ({ page }) => {
// Try to access protected route
await page.goto('/users/123')
// Should be on login page
await page.waitForURL(/\/login/)
// Login
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="password"]', 'password123')
await page.click('button[type="submit"]')
// Should redirect back to original destination
await expect(page).toHaveURL('/users/123')
})
})
```
### 6. Update Test Metadata
Add test to suite configuration:
```typescript
// e2e/test-registry.ts (auto-generated)
export const testRegistry = {
routes: [
'e2e/routes/users.$id.spec.ts',
// ... other routes
],
serverFunctions: [
'e2e/server-functions/create-user.spec.ts',
// ... other server functions
],
components: [
'e2e/components/user-card.spec.ts',
// ... other components
],
}
```
### 7. Generate Test Documentation
**Output**: `e2e/routes/users.$id.README.md`
```markdown
# User Profile Route Tests
## Test Coverage
- ✅ Route loading with valid user ID
- ✅ Loading state during navigation
- ✅ 404 handling for non-existent users
- ✅ Accessibility (zero violations)
- ✅ Back navigation
## Running Tests
```bash
# Run all tests for this route
pnpm test:e2e e2e/routes/users.$id.spec.ts
# Run specific test
pnpm test:e2e e2e/routes/users.$id.spec.ts -g "loads user profile"
# Debug mode
pnpm test:e2e:debug e2e/routes/users.$id.spec.ts
```
## Test Data
Uses test user ID: `123` (configured in test fixtures)
## Dependencies
- Requires D1 database with test data
- Requires user with ID 123 to exist
```
## Test Generation Patterns
### Pattern: Dynamic Route Parameters
For `/blog/$category/$slug`:
```typescript
test.describe('Blog Post Page', () => {
const testCategory = 'tech'
const testSlug = 'tanstack-start-guide'
test('loads blog post successfully', async ({ page }) => {
await page.goto(`/blog/${testCategory}/${testSlug}`)
await expect(page.locator('article')).toBeVisible()
await expect(page.locator('h1')).toBeVisible()
})
})
```
### Pattern: Search Params
For `/users?page=2&sort=name`:
```typescript
test.describe('Users List with Search Params', () => {
test('paginates users correctly', async ({ page }) => {
await page.goto('/users?page=2')
// Verify page 2 content
await expect(page.locator('[data-testid="pagination"]'))
.toContainText('Page 2')
})
test('sorts users by name', async ({ page }) => {
await page.goto('/users?sort=name')
const userNames = await page.locator('[data-testid="user-name"]').allTextContents()
// Verify sorted
const sorted = [...userNames].sort()
expect(userNames).toEqual(sorted)
})
})
```
## Validation
After generating tests:
1. **Syntax check**: Verify TypeScript compiles
2. **Dry run**: Run tests without executing
3. **Coverage**: Ensure critical paths covered
```bash
# Check syntax
npx tsc --noEmit
# Dry run
pnpm test:e2e --list
# Run generated tests
pnpm test:e2e e2e/routes/users.$id.spec.ts
```
## Success Criteria
✅ Tests generated for target
✅ All tests pass on first run
✅ Accessibility tests included
✅ Error handling covered
✅ Loading states tested
✅ Documentation generated
✅ Test registered in test suite
## Resources
- **Playwright Best Practices**: https://playwright.dev/docs/best-practices
- **Testing TanStack Router**: https://tanstack.com/router/latest/docs/framework/react/guide/testing
- **Accessibility Testing**: https://playwright.dev/docs/accessibility-testing