375 lines
11 KiB
Markdown
375 lines
11 KiB
Markdown
# Test File Generation Template
|
|
|
|
## Story Context
|
|
**Story**: {{Story Number}} - {{Story Title}}
|
|
**Test Execution Status**: {{Must be PASSED - verified through *test-story command}}
|
|
**Interactive Testing Fixes**: {{Capture any fixes discovered during Playwright MCP testing}}
|
|
|
|
## Test File Structure
|
|
|
|
### API Tests Template
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
// Define TypeScript interfaces for API responses
|
|
interface {{ResourceName}}Response {
|
|
id: number;
|
|
{{additional_fields_based_on_story}};
|
|
}
|
|
|
|
interface AuthResponse {
|
|
access: string;
|
|
refresh: string;
|
|
user: {
|
|
id: number;
|
|
email: string;
|
|
role: string;
|
|
};
|
|
}
|
|
|
|
interface ErrorResponse {
|
|
error: string;
|
|
details?: string[];
|
|
}
|
|
|
|
test.describe('{{Story Title}} - API Tests', () => {
|
|
let authToken: string;
|
|
let testUserId: number;
|
|
|
|
test.beforeAll(async ({ request }) => {
|
|
// Setup: Create test user and get authentication token
|
|
const registerResponse = await request.post('/api/auth/register/', {
|
|
data: {
|
|
email: 'test-{{timestamp}}@example.com',
|
|
password: 'SecurePass123!',
|
|
first_name: 'Test',
|
|
last_name: 'User',
|
|
organization_name: 'Test Org'
|
|
}
|
|
});
|
|
expect(registerResponse.status()).toBe(201);
|
|
|
|
const loginResponse = await request.post('/api/auth/login/', {
|
|
data: {
|
|
email: 'test-{{timestamp}}@example.com',
|
|
password: 'SecurePass123!'
|
|
}
|
|
});
|
|
expect(loginResponse.status()).toBe(200);
|
|
|
|
const authData: AuthResponse = await loginResponse.json();
|
|
authToken = authData.access;
|
|
testUserId = authData.user.id;
|
|
});
|
|
|
|
test.afterAll(async ({ request }) => {
|
|
// Cleanup: Remove test data
|
|
if (testUserId) {
|
|
await request.delete(`/api/users/${testUserId}/`, {
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
}
|
|
});
|
|
|
|
{{API_TEST_SCENARIOS}}
|
|
|
|
test('should handle authentication errors correctly', async ({ request }) => {
|
|
const response = await request.get('/api/{{protected_endpoint}}/', {
|
|
headers: { Authorization: 'Bearer invalid-token' }
|
|
});
|
|
|
|
expect(response.status()).toBe(401);
|
|
const errorData: ErrorResponse = await response.json();
|
|
expect(errorData).toHaveProperty('error');
|
|
});
|
|
|
|
test('should handle authorization errors correctly', async ({ request }) => {
|
|
// Test with valid token but insufficient permissions
|
|
const response = await request.get('/api/admin/{{admin_endpoint}}/', {
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.status()).toBe(403);
|
|
});
|
|
});
|
|
```
|
|
|
|
### E2E Tests Template
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('{{Story Title}} - E2E Tests', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Mock API responses for consistent testing
|
|
await page.route('**/api/auth/login/', async route => {
|
|
const request = route.request();
|
|
const body = await request.postDataJSON();
|
|
|
|
if (body.email === 'test@example.com' && body.password === 'SecurePass123!') {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
access: 'mock-jwt-token',
|
|
refresh: 'mock-refresh-token',
|
|
user: { id: 1, email: 'test@example.com', role: 'vendor' }
|
|
})
|
|
});
|
|
} else {
|
|
await route.fulfill({
|
|
status: 401,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Invalid credentials' })
|
|
});
|
|
}
|
|
});
|
|
|
|
{{ADDITIONAL_API_MOCKS}}
|
|
|
|
await page.goto('/');
|
|
});
|
|
|
|
{{E2E_TEST_SCENARIOS}}
|
|
|
|
test('should handle network errors gracefully', async ({ page }) => {
|
|
// Simulate network failure
|
|
await page.route('**/api/**', route => route.abort());
|
|
|
|
await page.goto('/{{test_page}}');
|
|
|
|
// Verify error handling
|
|
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="error-message"]')).toContainText('Network error');
|
|
});
|
|
|
|
test('should maintain accessibility standards', async ({ page }) => {
|
|
await page.goto('/{{test_page}}');
|
|
|
|
// Test keyboard navigation
|
|
await page.keyboard.press('Tab');
|
|
await expect(page.locator(':focus')).toBeVisible();
|
|
|
|
// Test ARIA labels
|
|
const form = page.locator('form');
|
|
await expect(form).toHaveAttribute('aria-label');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests Template
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('{{Story Title}} - Integration Tests', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Set up realistic API responses
|
|
await page.route('**/api/{{resource}}/**', async route => {
|
|
const method = route.request().method();
|
|
const url = route.request().url();
|
|
|
|
if (method === 'GET' && url.includes('/api/{{resource}}/')) {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({{mock_data}})
|
|
});
|
|
} else if (method === 'POST') {
|
|
const body = await route.request().postDataJSON();
|
|
await route.fulfill({
|
|
status: 201,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ id: Date.now(), ...body })
|
|
});
|
|
}
|
|
});
|
|
|
|
await page.goto('/{{test_page}}');
|
|
});
|
|
|
|
{{INTEGRATION_TEST_SCENARIOS}}
|
|
|
|
test('should handle concurrent operations correctly', async ({ page }) => {
|
|
// Test multiple simultaneous actions
|
|
const promises = [
|
|
page.click('[data-testid="action-1"]'),
|
|
page.click('[data-testid="action-2"]'),
|
|
page.click('[data-testid="action-3"]')
|
|
];
|
|
|
|
await Promise.all(promises);
|
|
|
|
// Verify final state is consistent
|
|
await expect(page.locator('[data-testid="status"]')).toContainText('All operations completed');
|
|
});
|
|
});
|
|
```
|
|
|
|
## Test Scenario Conversion Rules
|
|
|
|
### Natural Language → TypeScript Conversion
|
|
|
|
**Playwright MCP**: `Use Playwright_navigate to navigate to {{URL}}`
|
|
**TypeScript**: `await page.goto('{{URL}}');`
|
|
|
|
**Playwright MCP**: `Use Playwright_fill to fill {{field}} with selector "{{selector}}" and value "{{value}}"`
|
|
**TypeScript**: `await page.locator('{{selector}}').fill('{{value}}');`
|
|
|
|
**Playwright MCP**: `Use Playwright_click to click {{element}} with selector "{{selector}}"`
|
|
**TypeScript**: `await page.locator('{{selector}}').click();`
|
|
|
|
**Playwright MCP**: `Use Playwright_get to call {{API_URL}} with Authorization header`
|
|
**TypeScript**:
|
|
```typescript
|
|
const response = await request.get('{{API_URL}}', {
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
expect(response.status()).toBe(200);
|
|
```
|
|
|
|
**Playwright MCP**: `Use Playwright_post to call {{API_URL}} with JSON body`
|
|
**TypeScript**:
|
|
```typescript
|
|
const response = await request.post('{{API_URL}}', {
|
|
data: {{JSON_BODY}},
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
expect(response.status()).toBe(201);
|
|
```
|
|
|
|
**Playwright MCP**: `Use Playwright_screenshot to take screenshot named "{{name}}"`
|
|
**TypeScript**: `await page.screenshot({ path: 'screenshots/{{name}}.png' });`
|
|
|
|
**Playwright MCP**: `Use Playwright_console_logs to check for errors`
|
|
**TypeScript**:
|
|
```typescript
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
console.error('Console error:', msg.text());
|
|
}
|
|
});
|
|
```
|
|
|
|
**Playwright MCP**: `Use Playwright_get_visible_text to verify {{content}}`
|
|
**TypeScript**: `await expect(page.locator('{{selector}}')).toContainText('{{content}}');`
|
|
|
|
**Playwright MCP**: `Verify response status code is {{status}}`
|
|
**TypeScript**: `expect(response.status()).toBe({{status}});`
|
|
|
|
**Playwright MCP**: `Use Playwright_select to select {{option}} with selector "{{selector}}"`
|
|
**TypeScript**: `await page.locator('{{selector}}').selectOption('{{option}}');`
|
|
|
|
**Playwright MCP**: `Use Playwright_hover to hover over {{element}} with selector "{{selector}}"`
|
|
**TypeScript**: `await page.locator('{{selector}}').hover();`
|
|
|
|
**Playwright MCP**: `Use Playwright_press_key to press "{{key}}"`
|
|
**TypeScript**: `await page.keyboard.press('{{key}}');`
|
|
|
|
**Playwright MCP**: `Use Playwright_expect_response to monitor API calls with id "{{id}}"`
|
|
**TypeScript**:
|
|
```typescript
|
|
const responsePromise = page.waitForResponse(response =>
|
|
response.url().includes('{{endpoint}}') && response.status() === 200
|
|
);
|
|
```
|
|
|
|
**Playwright MCP**: `Use Playwright_assert_response to validate response with id "{{id}}"`
|
|
**TypeScript**:
|
|
```typescript
|
|
const response = await responsePromise;
|
|
const data = await response.json();
|
|
expect(data).toHaveProperty('{{expected_property}}');
|
|
```
|
|
|
|
## File Organization Structure
|
|
|
|
```
|
|
packages/e2e-tests/
|
|
├── tests/
|
|
│ ├── api/
|
|
│ │ └── story-{{story_number}}-{{story_name}}.spec.ts
|
|
│ ├── e2e/
|
|
│ │ └── story-{{story_number}}-{{story_name}}.spec.ts
|
|
│ └── integration/
|
|
│ └── story-{{story_number}}-{{story_name}}.spec.ts
|
|
├── fixtures/
|
|
│ └── story-{{story_number}}-test-data.ts
|
|
└── utils/
|
|
└── story-{{story_number}}-helpers.ts
|
|
```
|
|
|
|
## Test Data and Fixtures
|
|
|
|
```typescript
|
|
// fixtures/story-{{story_number}}-test-data.ts
|
|
export const testData = {
|
|
validUser: {
|
|
email: 'test-user@example.com',
|
|
password: 'SecurePass123!',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
organization: 'Test Organization'
|
|
},
|
|
invalidUser: {
|
|
email: 'invalid-email',
|
|
password: '123',
|
|
firstName: '',
|
|
lastName: ''
|
|
},
|
|
apiEndpoints: {
|
|
register: '/api/auth/register/',
|
|
login: '/api/auth/login/',
|
|
profile: '/api/users/profile/',
|
|
{{additional_endpoints}}
|
|
}
|
|
};
|
|
|
|
export const mockResponses = {
|
|
successfulLogin: {
|
|
access: 'mock-jwt-token',
|
|
refresh: 'mock-refresh-token',
|
|
user: { id: 1, email: 'test@example.com', role: 'vendor' }
|
|
},
|
|
registrationSuccess: {
|
|
message: 'Registration successful',
|
|
user: { id: 1, email: 'test@example.com' }
|
|
},
|
|
{{additional_mocks}}
|
|
};
|
|
```
|
|
|
|
## Helper Functions
|
|
|
|
```typescript
|
|
// utils/story-{{story_number}}-helpers.ts
|
|
import { Page, Request } from '@playwright/test';
|
|
|
|
export class TestHelpers {
|
|
static async loginUser(page: Page, email: string, password: string) {
|
|
await page.goto('/login');
|
|
await page.locator('[data-testid="email"]').fill(email);
|
|
await page.locator('[data-testid="password"]').fill(password);
|
|
await page.locator('[data-testid="login-button"]').click();
|
|
}
|
|
|
|
static async createTestUser(request: Request) {
|
|
const response = await request.post('/api/auth/register/', {
|
|
data: {
|
|
email: `test-${Date.now()}@example.com`,
|
|
password: 'SecurePass123!',
|
|
first_name: 'Test',
|
|
last_name: 'User'
|
|
}
|
|
});
|
|
return response.json();
|
|
}
|
|
|
|
static async cleanupTestData(request: Request, userId: number, authToken: string) {
|
|
await request.delete(`/api/users/${userId}/`, {
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
}
|
|
|
|
{{additional_helpers}}
|
|
}
|
|
```
|