qa-workflow-cc 1.0.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 +461 -0
- package/VERSION +1 -0
- package/bin/install.js +116 -0
- package/commands/qa/continue.md +77 -0
- package/commands/qa/full.md +149 -0
- package/commands/qa/init.md +105 -0
- package/commands/qa/resume.md +91 -0
- package/commands/qa/status.md +66 -0
- package/package.json +28 -0
- package/skills/qa/SKILL.md +420 -0
- package/skills/qa/references/continuation-format.md +58 -0
- package/skills/qa/references/exit-criteria.md +53 -0
- package/skills/qa/references/lifecycle.md +181 -0
- package/skills/qa/references/model-profiles.md +77 -0
- package/skills/qa/templates/agent-skeleton.md +733 -0
- package/skills/qa/templates/component-test.md +1088 -0
- package/skills/qa/templates/domain-research-queries.md +101 -0
- package/skills/qa/templates/domain-security-profiles.md +182 -0
- package/skills/qa/templates/e2e-test.md +1200 -0
- package/skills/qa/templates/nielsen-heuristics.md +274 -0
- package/skills/qa/templates/performance-benchmarks-base.md +321 -0
- package/skills/qa/templates/qa-report-template.md +271 -0
- package/skills/qa/templates/security-checklist-owasp.md +451 -0
- package/skills/qa/templates/stop-points/bootstrap-complete.md +36 -0
- package/skills/qa/templates/stop-points/certified.md +25 -0
- package/skills/qa/templates/stop-points/escalated.md +32 -0
- package/skills/qa/templates/stop-points/fix-ready.md +43 -0
- package/skills/qa/templates/stop-points/phase-transition.md +4 -0
- package/skills/qa/templates/stop-points/status-dashboard.md +32 -0
- package/skills/qa/templates/test-standards.md +652 -0
- package/skills/qa/templates/unit-test.md +998 -0
- package/skills/qa/templates/visual-regression.md +418 -0
- package/skills/qa/workflows/bootstrap.md +45 -0
- package/skills/qa/workflows/decision-gate.md +66 -0
- package/skills/qa/workflows/fix-execute.md +132 -0
- package/skills/qa/workflows/fix-plan.md +52 -0
- package/skills/qa/workflows/report-phase.md +64 -0
- package/skills/qa/workflows/test-phase.md +86 -0
- package/skills/qa/workflows/verify-phase.md +65 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
> Generic testing standards template. Load this skill when writing tests. Not needed every session.
|
|
4
|
+
|
|
5
|
+
## Template Variables
|
|
6
|
+
|
|
7
|
+
| Variable | Description | Default |
|
|
8
|
+
|----------|-------------|---------|
|
|
9
|
+
| `{{testRunner}}` | Test runner name (vitest, jest) | vitest |
|
|
10
|
+
| `{{testCommand}}` | Command to run tests | pnpm test |
|
|
11
|
+
| `{{testCoverageCommand}}` | Command for coverage report | pnpm test:coverage |
|
|
12
|
+
| `{{e2eCommand}}` | Command to run E2E tests | pnpm e2e |
|
|
13
|
+
| `{{coverageThresholds}}` | Coverage threshold block | See defaults below |
|
|
14
|
+
| `{{testCommands}}` | Project-specific test commands block | See defaults below |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Coverage Thresholds
|
|
19
|
+
|
|
20
|
+
### Global Defaults
|
|
21
|
+
|
|
22
|
+
{{coverageThresholds}}
|
|
23
|
+
|
|
24
|
+
| Metric | Threshold |
|
|
25
|
+
|--------|-----------|
|
|
26
|
+
| Statements | 80% |
|
|
27
|
+
| Branches | 70% |
|
|
28
|
+
| Functions | 80% |
|
|
29
|
+
| Lines | 80% |
|
|
30
|
+
|
|
31
|
+
### Coverage by Code Area
|
|
32
|
+
|
|
33
|
+
Define coverage targets by area based on criticality:
|
|
34
|
+
|
|
35
|
+
| Area | Target | Priority | Rationale |
|
|
36
|
+
|------|--------|----------|-----------|
|
|
37
|
+
| API / Server Routes | 90% | CRITICAL | Data integrity, security boundaries |
|
|
38
|
+
| Authentication / Authorization | 95% | CRITICAL | Security-critical code paths |
|
|
39
|
+
| Data Access Layer | 85% | HIGH | Database queries, data transformations |
|
|
40
|
+
| Utility Functions | 85% | HIGH | Widely reused, high leverage |
|
|
41
|
+
| Hooks / State Management | 80% | HIGH | Complex state logic |
|
|
42
|
+
| UI Components | 70% | MEDIUM | Visual correctness via snapshot + behavior tests |
|
|
43
|
+
| E2E Flows | Critical paths only | CRITICAL | User journeys that must never break |
|
|
44
|
+
| Configuration / Constants | 50% | LOW | Mostly static, low risk |
|
|
45
|
+
|
|
46
|
+
> **Project-specific override:** Replace the table above with your project's actual coverage targets during bootstrapping.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Test Type Requirements
|
|
51
|
+
|
|
52
|
+
### Unit Tests (Required for all new logic)
|
|
53
|
+
|
|
54
|
+
Every new utility function, hook, or pure logic module MUST have unit tests.
|
|
55
|
+
|
|
56
|
+
**File naming:** `[module].test.ts` or `__tests__/[module].test.ts`
|
|
57
|
+
|
|
58
|
+
**What to test:**
|
|
59
|
+
- Input/output mapping for all cases (happy path, edge cases, errors)
|
|
60
|
+
- Boundary conditions (empty, null, undefined, max values)
|
|
61
|
+
- Error throwing and error messages
|
|
62
|
+
- Default values and optional parameters
|
|
63
|
+
|
|
64
|
+
**Example areas:**
|
|
65
|
+
- Utility functions (formatters, validators, calculators)
|
|
66
|
+
- Custom hooks (state changes, effects, cleanup)
|
|
67
|
+
- Data transformation functions
|
|
68
|
+
- Schema validation logic (Zod, Yup, etc.)
|
|
69
|
+
- Business logic modules
|
|
70
|
+
|
|
71
|
+
### API / Router Tests (Required for all endpoints)
|
|
72
|
+
|
|
73
|
+
Every API endpoint or server-side route MUST have integration tests covering:
|
|
74
|
+
|
|
75
|
+
- CRUD operations (create, read, update, delete)
|
|
76
|
+
- Input validation (valid, invalid, edge cases)
|
|
77
|
+
- Authorization checks (authenticated vs. unauthenticated)
|
|
78
|
+
- Multi-tenancy isolation (if applicable -- data scoped to organization/user)
|
|
79
|
+
- Error handling (not found, forbidden, conflict, bad request)
|
|
80
|
+
- Pagination (cursor-based or offset-based)
|
|
81
|
+
|
|
82
|
+
**File naming:** `routers/__tests__/[router].test.ts` or `routes/__tests__/[route].test.ts`
|
|
83
|
+
|
|
84
|
+
### Component Tests (Required for complex components)
|
|
85
|
+
|
|
86
|
+
Cover:
|
|
87
|
+
- Rendering with different props (variants, states)
|
|
88
|
+
- User interactions (clicks, form submission, keyboard)
|
|
89
|
+
- Loading, error, and empty states
|
|
90
|
+
- Accessibility (ARIA attributes, keyboard navigation)
|
|
91
|
+
- Conditional rendering logic
|
|
92
|
+
|
|
93
|
+
**Use Testing Library patterns, not implementation details.**
|
|
94
|
+
|
|
95
|
+
**File naming:** `components/__tests__/[Component].test.tsx`
|
|
96
|
+
|
|
97
|
+
### E2E Tests (Required for critical flows)
|
|
98
|
+
|
|
99
|
+
Cover the critical user journeys that MUST NOT break:
|
|
100
|
+
|
|
101
|
+
- Authentication flow (sign up, sign in, sign out)
|
|
102
|
+
- Primary CRUD operations (create, edit, delete core entities)
|
|
103
|
+
- Navigation between major sections
|
|
104
|
+
- Form wizards / multi-step flows
|
|
105
|
+
- Payment / checkout flows (if applicable)
|
|
106
|
+
- File upload flows (if applicable)
|
|
107
|
+
|
|
108
|
+
**File naming:** `e2e/[flow-name].spec.ts`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Test Patterns
|
|
113
|
+
|
|
114
|
+
### DO (Recommended Patterns)
|
|
115
|
+
|
|
116
|
+
| Pattern | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| Semantic queries | `getByRole`, `getByLabelText`, `getByText` first |
|
|
119
|
+
| `userEvent` | Simulates real user interactions (not `fireEvent` for web) |
|
|
120
|
+
| `waitFor` / `findBy` | Handles async rendering and data fetching |
|
|
121
|
+
| Behavior testing | Test what the user sees and does, not internal state |
|
|
122
|
+
| `describe` blocks | Group related tests for readability |
|
|
123
|
+
| `it.each` / parameterized | Reduce duplication for similar test cases |
|
|
124
|
+
| Factory functions | Create test data with sensible defaults |
|
|
125
|
+
| Custom render | Wrap providers in test-utils for consistent setup |
|
|
126
|
+
| Mock cleanup | `vi.clearAllMocks()` or `jest.clearAllMocks()` in `beforeEach` |
|
|
127
|
+
| Fake timers | `vi.useFakeTimers()` for time-dependent code |
|
|
128
|
+
|
|
129
|
+
### DON'T (Anti-Patterns)
|
|
130
|
+
|
|
131
|
+
| Anti-Pattern | Why |
|
|
132
|
+
|--------------|-----|
|
|
133
|
+
| `getByClassName` | Implementation detail, breaks on refactor |
|
|
134
|
+
| `getByTestId` as default | Use semantic queries first, testId as last resort |
|
|
135
|
+
| Hardcoded waits (`sleep(2000)`) | Flaky, slow -- use `waitFor` or `findBy` |
|
|
136
|
+
| Testing internal state | Test behavior, not `useState` values |
|
|
137
|
+
| Testing implementation details | Internal methods, private functions, CSS classes |
|
|
138
|
+
| Snapshot-only tests | Snapshots detect changes but don't verify correctness |
|
|
139
|
+
| Shared mutable state | Each test must be independent |
|
|
140
|
+
| `any` in test types | Weakens test reliability |
|
|
141
|
+
| Console.log debugging left in | Clean up before committing |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Multi-Tenancy / Isolation Testing (CRITICAL)
|
|
146
|
+
|
|
147
|
+
If your application is multi-tenant (organizations, workspaces, teams), every database query test MUST verify isolation:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
describe('data isolation', () => {
|
|
151
|
+
it('does not return other tenant data', async () => {
|
|
152
|
+
// Create data for Org A
|
|
153
|
+
const itemA = await createItem({ orgId: 'org-a', name: 'Item A' })
|
|
154
|
+
|
|
155
|
+
// Query as Org B -- should NOT see Org A's data
|
|
156
|
+
const result = await listItems({ orgId: 'org-b' })
|
|
157
|
+
|
|
158
|
+
expect(result.items).not.toContainEqual(
|
|
159
|
+
expect.objectContaining({ id: itemA.id })
|
|
160
|
+
)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('rejects access to other tenant resources', async () => {
|
|
164
|
+
const item = await createItem({ orgId: 'org-a', name: 'Private Item' })
|
|
165
|
+
|
|
166
|
+
// Try to access as different org
|
|
167
|
+
await expect(
|
|
168
|
+
getItem({ orgId: 'org-b', itemId: item.id })
|
|
169
|
+
).rejects.toThrow() // Should throw FORBIDDEN or NOT_FOUND
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('scopes mutations to current tenant', async () => {
|
|
173
|
+
const item = await createItem({ orgId: 'org-a', name: 'Original' })
|
|
174
|
+
|
|
175
|
+
// Try to update as different org
|
|
176
|
+
await expect(
|
|
177
|
+
updateItem({ orgId: 'org-b', itemId: item.id, name: 'Hacked' })
|
|
178
|
+
).rejects.toThrow()
|
|
179
|
+
|
|
180
|
+
// Verify original is unchanged
|
|
181
|
+
const unchanged = await getItem({ orgId: 'org-a', itemId: item.id })
|
|
182
|
+
expect(unchanged.name).toBe('Original')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Checklist for isolation:**
|
|
188
|
+
- [ ] All list queries filter by tenant identifier
|
|
189
|
+
- [ ] All single-resource queries verify tenant ownership
|
|
190
|
+
- [ ] All mutations verify tenant ownership before modifying
|
|
191
|
+
- [ ] Aggregation queries (counts, sums) scope to tenant
|
|
192
|
+
- [ ] Search/filter queries scope to tenant
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Test Data Patterns
|
|
197
|
+
|
|
198
|
+
### Factory Functions
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// test/factories.ts
|
|
202
|
+
let idCounter = 0
|
|
203
|
+
|
|
204
|
+
export function createTestUser(overrides: Partial<User> = {}): User {
|
|
205
|
+
idCounter++
|
|
206
|
+
return {
|
|
207
|
+
id: `user-${idCounter}`,
|
|
208
|
+
name: `Test User ${idCounter}`,
|
|
209
|
+
email: `user${idCounter}@test.com`,
|
|
210
|
+
role: 'member',
|
|
211
|
+
createdAt: new Date(),
|
|
212
|
+
...overrides,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function createTestItem(overrides: Partial<Item> = {}): Item {
|
|
217
|
+
idCounter++
|
|
218
|
+
return {
|
|
219
|
+
id: `item-${idCounter}`,
|
|
220
|
+
name: `Test Item ${idCounter}`,
|
|
221
|
+
status: 'DRAFT',
|
|
222
|
+
createdAt: new Date(),
|
|
223
|
+
...overrides,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Usage
|
|
228
|
+
const user = createTestUser({ role: 'admin' })
|
|
229
|
+
const item = createTestItem({ status: 'ACTIVE', name: 'Custom Name' })
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Builder Pattern (For Complex Objects)
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
export class TestItemBuilder {
|
|
236
|
+
private item: Partial<Item> = {}
|
|
237
|
+
|
|
238
|
+
withName(name: string) { this.item.name = name; return this }
|
|
239
|
+
withStatus(status: string) { this.item.status = status; return this }
|
|
240
|
+
withOwner(userId: string) { this.item.ownerId = userId; return this }
|
|
241
|
+
withTags(tags: string[]) { this.item.tags = tags; return this }
|
|
242
|
+
|
|
243
|
+
build(): Item {
|
|
244
|
+
return createTestItem(this.item)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Usage
|
|
249
|
+
const item = new TestItemBuilder()
|
|
250
|
+
.withName('Important')
|
|
251
|
+
.withStatus('ACTIVE')
|
|
252
|
+
.withTags(['urgent', 'client'])
|
|
253
|
+
.build()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Test Organization
|
|
259
|
+
|
|
260
|
+
### Directory Structure
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
project/
|
|
264
|
+
src/
|
|
265
|
+
__tests__/ # Unit tests (mirrors src/ structure)
|
|
266
|
+
utils/
|
|
267
|
+
currency.test.ts
|
|
268
|
+
date.test.ts
|
|
269
|
+
hooks/
|
|
270
|
+
useDebounce.test.ts
|
|
271
|
+
services/
|
|
272
|
+
email.test.ts
|
|
273
|
+
components/
|
|
274
|
+
__tests__/ # Component tests (co-located)
|
|
275
|
+
ItemCard.test.tsx
|
|
276
|
+
Dashboard.test.tsx
|
|
277
|
+
e2e/ # E2E tests
|
|
278
|
+
fixtures/
|
|
279
|
+
auth.ts
|
|
280
|
+
testData.ts
|
|
281
|
+
pages/ # Page objects
|
|
282
|
+
ItemsPage.ts
|
|
283
|
+
DashboardPage.ts
|
|
284
|
+
item-management.spec.ts
|
|
285
|
+
auth-flow.spec.ts
|
|
286
|
+
test/ # Test utilities and setup
|
|
287
|
+
setup.ts
|
|
288
|
+
factories.ts
|
|
289
|
+
test-utils.tsx
|
|
290
|
+
mocks/
|
|
291
|
+
handlers.ts
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Test Naming Guidelines
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Describe blocks: noun (the thing being tested)
|
|
298
|
+
describe('formatCurrency', () => { ... })
|
|
299
|
+
describe('ItemCard', () => { ... })
|
|
300
|
+
describe('useDebounce', () => { ... })
|
|
301
|
+
|
|
302
|
+
// Test names: "it [does something expected]"
|
|
303
|
+
it('formats positive numbers with commas', () => { ... })
|
|
304
|
+
it('renders item name and status', () => { ... })
|
|
305
|
+
it('debounces rapid value changes', () => { ... })
|
|
306
|
+
|
|
307
|
+
// Group by scenario
|
|
308
|
+
describe('ItemCard', () => {
|
|
309
|
+
describe('when item is active', () => { ... })
|
|
310
|
+
describe('when item is archived', () => { ... })
|
|
311
|
+
describe('user interactions', () => { ... })
|
|
312
|
+
describe('accessibility', () => { ... })
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Security Testing Patterns
|
|
319
|
+
|
|
320
|
+
### Authentication Boundary Tests
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
describe('authentication boundaries', () => {
|
|
324
|
+
it('rejects unauthenticated requests', async () => {
|
|
325
|
+
await expect(
|
|
326
|
+
callEndpoint({ authToken: undefined })
|
|
327
|
+
).rejects.toThrow('UNAUTHORIZED')
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('rejects expired tokens', async () => {
|
|
331
|
+
await expect(
|
|
332
|
+
callEndpoint({ authToken: expiredToken })
|
|
333
|
+
).rejects.toThrow('UNAUTHORIZED')
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('rejects invalid tokens', async () => {
|
|
337
|
+
await expect(
|
|
338
|
+
callEndpoint({ authToken: 'invalid-garbage' })
|
|
339
|
+
).rejects.toThrow('UNAUTHORIZED')
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Authorization Tests
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
describe('authorization', () => {
|
|
348
|
+
it('allows admin to delete items', async () => {
|
|
349
|
+
const result = await deleteItem({ role: 'admin', itemId: '1' })
|
|
350
|
+
expect(result.success).toBe(true)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('forbids regular user from deleting items', async () => {
|
|
354
|
+
await expect(
|
|
355
|
+
deleteItem({ role: 'member', itemId: '1' })
|
|
356
|
+
).rejects.toThrow('FORBIDDEN')
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('allows item owner to edit their own item', async () => {
|
|
360
|
+
const result = await editItem({ userId: 'owner-1', itemId: '1' })
|
|
361
|
+
expect(result.success).toBe(true)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('forbids editing another user item', async () => {
|
|
365
|
+
await expect(
|
|
366
|
+
editItem({ userId: 'other-user', itemId: '1' })
|
|
367
|
+
).rejects.toThrow('FORBIDDEN')
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Input Sanitization Tests
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
describe('input sanitization', () => {
|
|
376
|
+
it('strips HTML from user input', () => {
|
|
377
|
+
const result = sanitize('<script>alert("xss")</script>Hello')
|
|
378
|
+
expect(result).toBe('Hello')
|
|
379
|
+
expect(result).not.toContain('<script>')
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('rejects SQL injection attempts', async () => {
|
|
383
|
+
await expect(
|
|
384
|
+
searchItems({ query: "'; DROP TABLE items; --" })
|
|
385
|
+
).resolves.not.toThrow()
|
|
386
|
+
// Items table should still exist
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Performance Testing Patterns
|
|
394
|
+
|
|
395
|
+
### Response Time Assertions
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
describe('performance', () => {
|
|
399
|
+
it('list query responds within 300ms', async () => {
|
|
400
|
+
const start = performance.now()
|
|
401
|
+
await listItems({ limit: 20 })
|
|
402
|
+
const duration = performance.now() - start
|
|
403
|
+
|
|
404
|
+
expect(duration).toBeLessThan(300)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('search responds within 500ms', async () => {
|
|
408
|
+
const start = performance.now()
|
|
409
|
+
await searchItems({ query: 'test' })
|
|
410
|
+
const duration = performance.now() - start
|
|
411
|
+
|
|
412
|
+
expect(duration).toBeLessThan(500)
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Lighthouse CI (E2E)
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// e2e/performance.spec.ts
|
|
421
|
+
test('meets Lighthouse performance targets', async ({ page }) => {
|
|
422
|
+
await page.goto('{{baseUrl}}/dashboard')
|
|
423
|
+
|
|
424
|
+
// Check critical metrics
|
|
425
|
+
const metrics = await page.evaluate(() => {
|
|
426
|
+
return new Promise(resolve => {
|
|
427
|
+
new PerformanceObserver(list => {
|
|
428
|
+
const entries = list.getEntries()
|
|
429
|
+
resolve({
|
|
430
|
+
lcp: entries.find(e => e.entryType === 'largest-contentful-paint')?.startTime,
|
|
431
|
+
fid: entries.find(e => e.entryType === 'first-input')?.processingStart,
|
|
432
|
+
})
|
|
433
|
+
}).observe({ entryTypes: ['largest-contentful-paint', 'first-input'] })
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Performance Benchmarks
|
|
442
|
+
|
|
443
|
+
| Metric | Target | Priority |
|
|
444
|
+
|--------|--------|----------|
|
|
445
|
+
| API simple queries | p95 < 300ms | HIGH |
|
|
446
|
+
| API mutations | p95 < 500ms | HIGH |
|
|
447
|
+
| API complex queries | p95 < 1000ms | MEDIUM |
|
|
448
|
+
| Lighthouse Performance | >= 80 | HIGH |
|
|
449
|
+
| Lighthouse Accessibility | >= 85 | HIGH |
|
|
450
|
+
| Lighthouse Best Practices | >= 90 | MEDIUM |
|
|
451
|
+
| Time to Interactive (TTI) | < 3.5s | HIGH |
|
|
452
|
+
| Largest Contentful Paint (LCP) | < 2.5s | HIGH |
|
|
453
|
+
| Cumulative Layout Shift (CLS) | < 0.1 | MEDIUM |
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## CI Integration
|
|
458
|
+
|
|
459
|
+
### Running Tests in CI
|
|
460
|
+
|
|
461
|
+
{{testCommands}}
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
# Unit + Integration tests
|
|
465
|
+
{{testCommand}}
|
|
466
|
+
|
|
467
|
+
# With coverage report
|
|
468
|
+
{{testCoverageCommand}}
|
|
469
|
+
|
|
470
|
+
# E2E tests (headless)
|
|
471
|
+
CI=true {{e2eCommand}}
|
|
472
|
+
|
|
473
|
+
# Run only changed files (for PR checks)
|
|
474
|
+
{{testCommand}} --changed
|
|
475
|
+
|
|
476
|
+
# Generate coverage report for upload
|
|
477
|
+
{{testCoverageCommand}} --reporter=json --outputFile=coverage.json
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### CI Pipeline Stages
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
1. Lint → eslint, prettier check
|
|
484
|
+
2. Type Check → tsc --noEmit
|
|
485
|
+
3. Unit Tests → {{testCommand}} --coverage
|
|
486
|
+
4. Build → build the application
|
|
487
|
+
5. E2E Tests → {{e2eCommand}} (against build)
|
|
488
|
+
6. Coverage Gate → check thresholds
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Coverage Report Upload
|
|
492
|
+
|
|
493
|
+
```yaml
|
|
494
|
+
# Example CI step (GitHub Actions)
|
|
495
|
+
- name: Upload coverage
|
|
496
|
+
uses: codecov/codecov-action@v4
|
|
497
|
+
with:
|
|
498
|
+
file: ./coverage/coverage-final.json
|
|
499
|
+
fail_ci_if_error: true
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## When to Write Tests
|
|
505
|
+
|
|
506
|
+
| Scenario | Test Type | Priority |
|
|
507
|
+
|----------|-----------|----------|
|
|
508
|
+
| New API endpoint / route | Unit + Integration | REQUIRED |
|
|
509
|
+
| New utility function | Unit | REQUIRED |
|
|
510
|
+
| New custom hook | Unit | REQUIRED |
|
|
511
|
+
| New page / screen | Component + E2E | REQUIRED |
|
|
512
|
+
| New complex component | Component | REQUIRED |
|
|
513
|
+
| New form | Component (validation) + E2E | REQUIRED |
|
|
514
|
+
| Bug fix | Regression test first | REQUIRED |
|
|
515
|
+
| Refactoring | Verify existing tests pass | REQUIRED |
|
|
516
|
+
| Performance optimization | Benchmark before/after | RECOMMENDED |
|
|
517
|
+
| Security fix | Security test case | REQUIRED |
|
|
518
|
+
| Config change | Verify build still works | REQUIRED |
|
|
519
|
+
| Dependency update | Run full suite | REQUIRED |
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## QA Program Integration
|
|
524
|
+
|
|
525
|
+
Test standards are part of a broader QA program. When bootstrapping a new project, create these QA resources:
|
|
526
|
+
|
|
527
|
+
### Resource Files to Generate
|
|
528
|
+
|
|
529
|
+
| Resource | Purpose |
|
|
530
|
+
|----------|---------|
|
|
531
|
+
| `test-matrix.md` | Map features to test cases |
|
|
532
|
+
| `security-checklist.md` | OWASP + auth + isolation checks |
|
|
533
|
+
| `nielsen-heuristics.md` | UX quality rubric |
|
|
534
|
+
| `performance-benchmarks.md` | Response time budgets |
|
|
535
|
+
| `qa-report-template.md` | Standardized QA report format |
|
|
536
|
+
|
|
537
|
+
### QA Agents (When Using Agent Framework)
|
|
538
|
+
|
|
539
|
+
| Agent | Role |
|
|
540
|
+
|-------|------|
|
|
541
|
+
| `qa-test-executor` | Runs functional + API tests |
|
|
542
|
+
| `qa-security-auditor` | Auth boundaries + data isolation |
|
|
543
|
+
| `qa-ux-optimizer` | Heuristic evaluation + WCAG |
|
|
544
|
+
| `qa-report-writer` | Consolidates findings |
|
|
545
|
+
| `qa-fix-planner` | Prioritizes defects |
|
|
546
|
+
| `qa-verifier` | Re-runs failed tests after fixes |
|
|
547
|
+
|
|
548
|
+
### QA Cycle
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
1. Plan tests (from PRD / feature spec)
|
|
552
|
+
2. Write tests (using skill templates)
|
|
553
|
+
3. Execute tests (via test runner + E2E)
|
|
554
|
+
4. Report findings (qa-report-template)
|
|
555
|
+
5. Fix defects (prioritized by severity)
|
|
556
|
+
6. Re-verify fixes (regression suite)
|
|
557
|
+
7. Update test matrix (mark as covered)
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Test Configuration Templates
|
|
563
|
+
|
|
564
|
+
### Vitest Config
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// vitest.config.ts
|
|
568
|
+
import { defineConfig } from 'vitest/config'
|
|
569
|
+
import path from 'path'
|
|
570
|
+
|
|
571
|
+
export default defineConfig({
|
|
572
|
+
test: {
|
|
573
|
+
globals: true,
|
|
574
|
+
environment: 'jsdom', // or 'node' for API tests
|
|
575
|
+
setupFiles: ['./test/setup.ts'],
|
|
576
|
+
include: ['**/*.test.{ts,tsx}'],
|
|
577
|
+
exclude: ['node_modules', 'dist', 'e2e'],
|
|
578
|
+
coverage: {
|
|
579
|
+
provider: 'v8',
|
|
580
|
+
reporter: ['text', 'json', 'html'],
|
|
581
|
+
thresholds: {
|
|
582
|
+
statements: 80,
|
|
583
|
+
branches: 70,
|
|
584
|
+
functions: 80,
|
|
585
|
+
lines: 80,
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
resolve: {
|
|
590
|
+
alias: {
|
|
591
|
+
'@': path.resolve(__dirname, './src'),
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
})
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Jest Config
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
// jest.config.ts
|
|
601
|
+
import type { Config } from 'jest'
|
|
602
|
+
|
|
603
|
+
const config: Config = {
|
|
604
|
+
preset: 'ts-jest',
|
|
605
|
+
testEnvironment: 'jsdom', // or 'node'
|
|
606
|
+
setupFilesAfterSetup: ['./test/setup.ts'],
|
|
607
|
+
moduleNameMapper: {
|
|
608
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
|
609
|
+
},
|
|
610
|
+
coverageThreshold: {
|
|
611
|
+
global: {
|
|
612
|
+
statements: 80,
|
|
613
|
+
branches: 70,
|
|
614
|
+
functions: 80,
|
|
615
|
+
lines: 80,
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
collectCoverageFrom: [
|
|
619
|
+
'src/**/*.{ts,tsx}',
|
|
620
|
+
'!src/**/*.d.ts',
|
|
621
|
+
'!src/**/index.ts',
|
|
622
|
+
],
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export default config
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Test Setup File
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// test/setup.ts
|
|
632
|
+
import '@testing-library/jest-dom' // or '@testing-library/jest-dom/vitest'
|
|
633
|
+
|
|
634
|
+
// Global mocks
|
|
635
|
+
beforeAll(() => {
|
|
636
|
+
// Mock environment variables for tests
|
|
637
|
+
process.env.NODE_ENV = 'test'
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
afterEach(() => {
|
|
641
|
+
// Clean up between tests
|
|
642
|
+
vi.clearAllMocks() // or jest.clearAllMocks()
|
|
643
|
+
})
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## See Also
|
|
649
|
+
|
|
650
|
+
- `unit-test.md` - Detailed unit test patterns
|
|
651
|
+
- `component-test.md` - Component test patterns
|
|
652
|
+
- `e2e-test.md` - End-to-end test patterns
|