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.
@@ -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
+ ```