agentic-team-templates 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/templates/product-manager/.cursorrules/communication.md +353 -0
- package/templates/product-manager/.cursorrules/discovery.md +258 -0
- package/templates/product-manager/.cursorrules/metrics.md +319 -0
- package/templates/product-manager/.cursorrules/overview.md +95 -0
- package/templates/product-manager/.cursorrules/prioritization.md +240 -0
- package/templates/product-manager/.cursorrules/requirements.md +371 -0
- package/templates/product-manager/CLAUDE.md +593 -0
- package/templates/qa-engineering/.cursorrules/automation.md +460 -0
- package/templates/qa-engineering/.cursorrules/metrics.md +292 -0
- package/templates/qa-engineering/.cursorrules/overview.md +125 -0
- package/templates/qa-engineering/.cursorrules/quality-gates.md +372 -0
- package/templates/qa-engineering/.cursorrules/test-design.md +301 -0
- package/templates/qa-engineering/.cursorrules/test-strategy.md +218 -0
- package/templates/qa-engineering/CLAUDE.md +726 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# Test Automation
|
|
2
|
+
|
|
3
|
+
Best practices for building maintainable test automation.
|
|
4
|
+
|
|
5
|
+
## Automation Strategy
|
|
6
|
+
|
|
7
|
+
### What to Automate
|
|
8
|
+
|
|
9
|
+
| Automate | Why |
|
|
10
|
+
|----------|-----|
|
|
11
|
+
| Regression tests | Run frequently, catch regressions early |
|
|
12
|
+
| Smoke tests | Fast feedback on deployments |
|
|
13
|
+
| API tests | Fast, reliable, good ROI |
|
|
14
|
+
| Data-driven tests | Same logic, many inputs |
|
|
15
|
+
| Performance baselines | Consistent measurement |
|
|
16
|
+
|
|
17
|
+
### What to Keep Manual
|
|
18
|
+
|
|
19
|
+
| Manual | Why |
|
|
20
|
+
|--------|-----|
|
|
21
|
+
| Exploratory testing | Requires human creativity |
|
|
22
|
+
| Usability testing | Subjective human judgment |
|
|
23
|
+
| New/changing features | Not stable enough to automate |
|
|
24
|
+
| One-time tests | ROI doesn't justify automation |
|
|
25
|
+
| Visual/UX assessment | Requires human perception |
|
|
26
|
+
|
|
27
|
+
### Automation ROI
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
ROI = (Manual Time × Frequency - Automation Time) / Automation Time
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
- Manual execution: 2 hours
|
|
34
|
+
- Run frequency: 50 times/year
|
|
35
|
+
- Automation creation: 8 hours
|
|
36
|
+
- Automation execution: 5 minutes
|
|
37
|
+
- Automation maintenance: 4 hours/year
|
|
38
|
+
|
|
39
|
+
Manual cost: 2h × 50 = 100 hours/year
|
|
40
|
+
Automation cost: 8h + (5min × 50) + 4h = 16 hours/year
|
|
41
|
+
Savings: 84 hours/year
|
|
42
|
+
ROI: (100 - 16) / 16 = 525%
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Test Structure
|
|
46
|
+
|
|
47
|
+
### Arrange-Act-Assert (AAA)
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
describe('ShoppingCart', () => {
|
|
51
|
+
it('calculates total with discount', () => {
|
|
52
|
+
// Arrange
|
|
53
|
+
const cart = new ShoppingCart();
|
|
54
|
+
cart.addItem({ name: 'Widget', price: 100, quantity: 2 });
|
|
55
|
+
cart.applyDiscount('SAVE10');
|
|
56
|
+
|
|
57
|
+
// Act
|
|
58
|
+
const total = cart.getTotal();
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(total).toBe(180); // 200 - 10%
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Given-When-Then (BDD)
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
describe('User Authentication', () => {
|
|
70
|
+
describe('given a registered user', () => {
|
|
71
|
+
const user = { email: 'test@example.com', password: 'ValidPass123!' };
|
|
72
|
+
|
|
73
|
+
describe('when they login with valid credentials', () => {
|
|
74
|
+
it('then they are redirected to dashboard', async () => {
|
|
75
|
+
const result = await login(user.email, user.password);
|
|
76
|
+
expect(result.redirect).toBe('/dashboard');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('then a session token is created', async () => {
|
|
80
|
+
const result = await login(user.email, user.password);
|
|
81
|
+
expect(result.token).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('when they login with wrong password', () => {
|
|
86
|
+
it('then an error is returned', async () => {
|
|
87
|
+
const result = await login(user.email, 'wrong');
|
|
88
|
+
expect(result.error).toBe('Invalid credentials');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Page Object Pattern
|
|
96
|
+
|
|
97
|
+
### Structure
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// pages/LoginPage.js
|
|
101
|
+
export class LoginPage {
|
|
102
|
+
constructor(page) {
|
|
103
|
+
this.page = page;
|
|
104
|
+
|
|
105
|
+
// Locators
|
|
106
|
+
this.emailInput = page.locator('[data-testid="email"]');
|
|
107
|
+
this.passwordInput = page.locator('[data-testid="password"]');
|
|
108
|
+
this.loginButton = page.locator('[data-testid="login-button"]');
|
|
109
|
+
this.errorMessage = page.locator('[data-testid="error-message"]');
|
|
110
|
+
this.forgotPasswordLink = page.locator('[data-testid="forgot-password"]');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Navigation
|
|
114
|
+
async goto() {
|
|
115
|
+
await this.page.goto('/login');
|
|
116
|
+
await this.page.waitForLoadState('networkidle');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Actions
|
|
120
|
+
async login(email, password) {
|
|
121
|
+
await this.emailInput.fill(email);
|
|
122
|
+
await this.passwordInput.fill(password);
|
|
123
|
+
await this.loginButton.click();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async clickForgotPassword() {
|
|
127
|
+
await this.forgotPasswordLink.click();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Assertions
|
|
131
|
+
async expectErrorMessage(message) {
|
|
132
|
+
await expect(this.errorMessage).toContainText(message);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async expectToBeOnDashboard() {
|
|
136
|
+
await expect(this.page).toHaveURL('/dashboard');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Using Page Objects
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
// tests/login.spec.js
|
|
145
|
+
import { test, expect } from '@playwright/test';
|
|
146
|
+
import { LoginPage } from '../pages/LoginPage';
|
|
147
|
+
import { DashboardPage } from '../pages/DashboardPage';
|
|
148
|
+
|
|
149
|
+
test.describe('Login', () => {
|
|
150
|
+
let loginPage;
|
|
151
|
+
|
|
152
|
+
test.beforeEach(async ({ page }) => {
|
|
153
|
+
loginPage = new LoginPage(page);
|
|
154
|
+
await loginPage.goto();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('successful login redirects to dashboard', async ({ page }) => {
|
|
158
|
+
await loginPage.login('valid@example.com', 'ValidPass123!');
|
|
159
|
+
|
|
160
|
+
const dashboard = new DashboardPage(page);
|
|
161
|
+
await dashboard.expectToBeVisible();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('invalid credentials shows error', async () => {
|
|
165
|
+
await loginPage.login('invalid@example.com', 'wrong');
|
|
166
|
+
await loginPage.expectErrorMessage('Invalid credentials');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Test Data Management
|
|
172
|
+
|
|
173
|
+
### Factories
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// factories/userFactory.js
|
|
177
|
+
export const createUser = (overrides = {}) => ({
|
|
178
|
+
id: `user-${Date.now()}`,
|
|
179
|
+
email: `test-${Date.now()}@example.com`,
|
|
180
|
+
firstName: 'Test',
|
|
181
|
+
lastName: 'User',
|
|
182
|
+
role: 'user',
|
|
183
|
+
createdAt: new Date().toISOString(),
|
|
184
|
+
...overrides
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export const createAdminUser = (overrides = {}) =>
|
|
188
|
+
createUser({ role: 'admin', ...overrides });
|
|
189
|
+
|
|
190
|
+
export const createPremiumUser = (overrides = {}) =>
|
|
191
|
+
createUser({ role: 'premium', subscription: 'active', ...overrides });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Fixtures
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// fixtures/products.js
|
|
198
|
+
export const products = {
|
|
199
|
+
widget: {
|
|
200
|
+
id: 'prod-001',
|
|
201
|
+
name: 'Widget',
|
|
202
|
+
price: 29.99,
|
|
203
|
+
category: 'electronics',
|
|
204
|
+
inStock: true
|
|
205
|
+
},
|
|
206
|
+
gadget: {
|
|
207
|
+
id: 'prod-002',
|
|
208
|
+
name: 'Gadget',
|
|
209
|
+
price: 99.99,
|
|
210
|
+
category: 'electronics',
|
|
211
|
+
inStock: false
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// fixtures/orders.js
|
|
216
|
+
export const orders = {
|
|
217
|
+
pending: {
|
|
218
|
+
id: 'ord-001',
|
|
219
|
+
status: 'pending',
|
|
220
|
+
total: 129.98,
|
|
221
|
+
items: [products.widget, products.gadget]
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Database Seeding
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// setup/seedDatabase.js
|
|
230
|
+
import { createUser, createAdminUser } from '../factories/userFactory';
|
|
231
|
+
import { products } from '../fixtures/products';
|
|
232
|
+
|
|
233
|
+
export async function seedTestDatabase(db) {
|
|
234
|
+
// Clear existing data
|
|
235
|
+
await db.clear();
|
|
236
|
+
|
|
237
|
+
// Seed users
|
|
238
|
+
const testUser = createUser({ email: 'test@example.com' });
|
|
239
|
+
const adminUser = createAdminUser({ email: 'admin@example.com' });
|
|
240
|
+
await db.users.insertMany([testUser, adminUser]);
|
|
241
|
+
|
|
242
|
+
// Seed products
|
|
243
|
+
await db.products.insertMany(Object.values(products));
|
|
244
|
+
|
|
245
|
+
return { testUser, adminUser, products };
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Handling Flaky Tests
|
|
250
|
+
|
|
251
|
+
### Common Causes
|
|
252
|
+
|
|
253
|
+
| Cause | Solution |
|
|
254
|
+
|-------|----------|
|
|
255
|
+
| Timing issues | Explicit waits, not arbitrary sleeps |
|
|
256
|
+
| Test order dependency | Isolate tests, clean state |
|
|
257
|
+
| Shared state | Each test manages own data |
|
|
258
|
+
| External services | Mock or stub dependencies |
|
|
259
|
+
| Race conditions | Proper synchronization |
|
|
260
|
+
|
|
261
|
+
### Explicit Waits
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// ❌ Bad: Arbitrary sleep
|
|
265
|
+
await page.click('#submit');
|
|
266
|
+
await page.waitForTimeout(5000);
|
|
267
|
+
|
|
268
|
+
// ✅ Good: Wait for specific condition
|
|
269
|
+
await page.click('#submit');
|
|
270
|
+
await page.waitForSelector('[data-testid="success-message"]');
|
|
271
|
+
|
|
272
|
+
// ✅ Better: Wait for network idle
|
|
273
|
+
await page.click('#submit');
|
|
274
|
+
await page.waitForLoadState('networkidle');
|
|
275
|
+
|
|
276
|
+
// ✅ Best: Wait for specific response
|
|
277
|
+
await Promise.all([
|
|
278
|
+
page.waitForResponse(resp => resp.url().includes('/api/submit')),
|
|
279
|
+
page.click('#submit')
|
|
280
|
+
]);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Test Isolation
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// ❌ Bad: Tests share state
|
|
287
|
+
let user;
|
|
288
|
+
|
|
289
|
+
beforeAll(async () => {
|
|
290
|
+
user = await createUser();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('test 1', async () => {
|
|
294
|
+
await user.update({ name: 'Changed' }); // Affects other tests!
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('test 2', async () => {
|
|
298
|
+
expect(user.name).toBe('Original'); // Fails!
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ✅ Good: Each test has own state
|
|
302
|
+
test('test 1', async () => {
|
|
303
|
+
const user = await createUser();
|
|
304
|
+
await user.update({ name: 'Changed' });
|
|
305
|
+
expect(user.name).toBe('Changed');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('test 2', async () => {
|
|
309
|
+
const user = await createUser();
|
|
310
|
+
expect(user.name).toBe('Original');
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Retry Strategy
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
// playwright.config.js
|
|
318
|
+
export default {
|
|
319
|
+
retries: process.env.CI ? 2 : 0,
|
|
320
|
+
|
|
321
|
+
// Report flaky tests
|
|
322
|
+
reporter: [
|
|
323
|
+
['list'],
|
|
324
|
+
['html', { open: 'never' }],
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// For individual flaky tests (temporary!)
|
|
329
|
+
test('known flaky test', async ({ page }) => {
|
|
330
|
+
test.info().annotations.push({ type: 'flaky', description: 'JIRA-123' });
|
|
331
|
+
// ... test code
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## CI/CD Integration
|
|
336
|
+
|
|
337
|
+
### Pipeline Configuration
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
# .github/workflows/test.yml
|
|
341
|
+
name: Test Suite
|
|
342
|
+
|
|
343
|
+
on:
|
|
344
|
+
push:
|
|
345
|
+
branches: [main, develop]
|
|
346
|
+
pull_request:
|
|
347
|
+
branches: [main]
|
|
348
|
+
|
|
349
|
+
jobs:
|
|
350
|
+
unit-tests:
|
|
351
|
+
runs-on: ubuntu-latest
|
|
352
|
+
steps:
|
|
353
|
+
- uses: actions/checkout@v4
|
|
354
|
+
- uses: actions/setup-node@v4
|
|
355
|
+
with:
|
|
356
|
+
node-version: '20'
|
|
357
|
+
cache: 'npm'
|
|
358
|
+
- run: npm ci
|
|
359
|
+
- run: npm run test:unit -- --coverage
|
|
360
|
+
- uses: codecov/codecov-action@v3
|
|
361
|
+
with:
|
|
362
|
+
files: ./coverage/lcov.info
|
|
363
|
+
|
|
364
|
+
integration-tests:
|
|
365
|
+
runs-on: ubuntu-latest
|
|
366
|
+
services:
|
|
367
|
+
postgres:
|
|
368
|
+
image: postgres:15
|
|
369
|
+
env:
|
|
370
|
+
POSTGRES_DB: test
|
|
371
|
+
POSTGRES_PASSWORD: test
|
|
372
|
+
options: >-
|
|
373
|
+
--health-cmd pg_isready
|
|
374
|
+
--health-interval 10s
|
|
375
|
+
--health-timeout 5s
|
|
376
|
+
--health-retries 5
|
|
377
|
+
steps:
|
|
378
|
+
- uses: actions/checkout@v4
|
|
379
|
+
- uses: actions/setup-node@v4
|
|
380
|
+
- run: npm ci
|
|
381
|
+
- run: npm run test:integration
|
|
382
|
+
env:
|
|
383
|
+
DATABASE_URL: postgresql://postgres:test@localhost:5432/test
|
|
384
|
+
|
|
385
|
+
e2e-tests:
|
|
386
|
+
runs-on: ubuntu-latest
|
|
387
|
+
steps:
|
|
388
|
+
- uses: actions/checkout@v4
|
|
389
|
+
- uses: actions/setup-node@v4
|
|
390
|
+
- run: npm ci
|
|
391
|
+
- run: npx playwright install --with-deps
|
|
392
|
+
- run: npm run test:e2e
|
|
393
|
+
- uses: actions/upload-artifact@v3
|
|
394
|
+
if: failure()
|
|
395
|
+
with:
|
|
396
|
+
name: playwright-report
|
|
397
|
+
path: playwright-report/
|
|
398
|
+
retention-days: 7
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Parallel Execution
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
// playwright.config.js
|
|
405
|
+
export default {
|
|
406
|
+
workers: process.env.CI ? 4 : undefined,
|
|
407
|
+
fullyParallel: true,
|
|
408
|
+
|
|
409
|
+
projects: [
|
|
410
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
411
|
+
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
|
412
|
+
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Reporting
|
|
418
|
+
|
|
419
|
+
### Test Report Structure
|
|
420
|
+
|
|
421
|
+
```markdown
|
|
422
|
+
## Test Execution Report
|
|
423
|
+
|
|
424
|
+
### Summary
|
|
425
|
+
| Metric | Value |
|
|
426
|
+
|--------|-------|
|
|
427
|
+
| Total Tests | 450 |
|
|
428
|
+
| Passed | 440 |
|
|
429
|
+
| Failed | 8 |
|
|
430
|
+
| Skipped | 2 |
|
|
431
|
+
| Duration | 12m 34s |
|
|
432
|
+
| Pass Rate | 97.8% |
|
|
433
|
+
|
|
434
|
+
### Failures
|
|
435
|
+
| Test | Error | Screenshot |
|
|
436
|
+
|------|-------|------------|
|
|
437
|
+
| login.spec.ts:15 | Timeout waiting for selector | [link] |
|
|
438
|
+
| checkout.spec.ts:42 | Expected 200, got 500 | [link] |
|
|
439
|
+
|
|
440
|
+
### Coverage
|
|
441
|
+
| Area | Coverage |
|
|
442
|
+
|------|----------|
|
|
443
|
+
| Statements | 84% |
|
|
444
|
+
| Branches | 76% |
|
|
445
|
+
| Functions | 89% |
|
|
446
|
+
| Lines | 84% |
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Screenshot on Failure
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
// playwright.config.js
|
|
453
|
+
export default {
|
|
454
|
+
use: {
|
|
455
|
+
screenshot: 'only-on-failure',
|
|
456
|
+
video: 'retain-on-failure',
|
|
457
|
+
trace: 'retain-on-failure',
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
```
|