Generate Playwright E2E tests for Tanstack Start routes, server functions, and components
Generate comprehensive Playwright E2E tests for Tanstack Start routes, server functions, and components. Use this when you need ready-to-run tests covering navigation, data loading, authentication, accessibility, and error handling.
/plugin marketplace add hirefrank/hirefrank-marketplace/plugin install edge-stack@hirefrank-marketplace<command_purpose> Automatically generate comprehensive Playwright tests for Tanstack Start routes, server functions, and components with Cloudflare Workers-specific patterns. </command_purpose>
<role>Senior QA Engineer specializing in test generation for Tanstack Start applications</role>
This command generates ready-to-use Playwright tests that cover:
⚠️ Security Note: When generating tests that interact with external websites or untrusted content, be aware that malicious web pages could attempt prompt injection attacks against browser automation agents. Review generated test code carefully, especially:
page.goto() calls to external sites)Best Practices:
For more information, see Anthropic's research on prompt injection defenses.
/es-test-gen <target> [options]
<target>: What to generate tests for
/users/$id, /dashboard, /blogsrc/lib/server-functions/createUser.tssrc/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 <path>: Custom output path# 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
# Determine target type
if [[ "$TARGET" == /* ]]; then
TYPE="route"
elif [[ "$TARGET" == *server-functions* ]]; then
TYPE="server-function"
elif [[ "$TARGET" == *components* ]]; then
TYPE="component"
fi
For route: /users/$id
Task playwright-testing-specialist(analyze route and generate tests):
Output: e2e/routes/users.$id.spec.ts
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('/')
})
})
For: src/lib/server-functions/createUser.ts
Output: e2e/server-functions/create-user.spec.ts
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)
})
})
For: src/components/UserCard.tsx
Output: e2e/components/user-card.spec.ts
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')
})
})
Output: e2e/auth/protected-route.spec.ts
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')
})
})
Add test to suite configuration:
// 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
],
}
Output: e2e/routes/users.$id.README.md
# 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
Uses test user ID: 123 (configured in test fixtures)
## 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()
})
})
For /users?page=2&sort=name:
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)
})
})
After generating tests:
# Check syntax
npx tsc --noEmit
# Dry run
pnpm test:e2e --list
# Run generated tests
pnpm test:e2e e2e/routes/users.$id.spec.ts
✅ 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