specweave 1.0.239 → 1.0.241

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.
Files changed (161) hide show
  1. package/CLAUDE.md +31 -30
  2. package/README.md +1 -1
  3. package/bin/specweave.js +16 -0
  4. package/dist/plugins/specweave-ado/lib/ado-permission-gate.d.ts.map +1 -1
  5. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js +17 -2
  6. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js.map +1 -1
  7. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +7 -0
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js +53 -0
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  11. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -1
  12. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +17 -2
  13. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -1
  14. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +1 -0
  15. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -1
  16. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +7 -3
  17. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -1
  18. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -1
  19. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +27 -19
  20. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -1
  21. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +8 -0
  22. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -1
  23. package/dist/plugins/specweave-testing/lib/playwright-routing.js +10 -7
  24. package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -1
  25. package/dist/src/adapters/agents-md-generator.js +1 -1
  26. package/dist/src/adapters/agents-md-generator.js.map +1 -1
  27. package/dist/src/adapters/claude/README.md +1 -1
  28. package/dist/src/adapters/claude-md-generator.js +1 -1
  29. package/dist/src/adapters/claude-md-generator.js.map +1 -1
  30. package/dist/src/cli/commands/init.d.ts.map +1 -1
  31. package/dist/src/cli/commands/init.js +10 -1
  32. package/dist/src/cli/commands/init.js.map +1 -1
  33. package/dist/src/cli/commands/refresh-marketplace.d.ts.map +1 -1
  34. package/dist/src/cli/commands/refresh-marketplace.js +7 -67
  35. package/dist/src/cli/commands/refresh-marketplace.js.map +1 -1
  36. package/dist/src/cli/commands/team.d.ts +20 -0
  37. package/dist/src/cli/commands/team.d.ts.map +1 -0
  38. package/dist/src/cli/commands/team.js +101 -0
  39. package/dist/src/cli/commands/team.js.map +1 -0
  40. package/dist/src/cli/helpers/init/claude-settings-env.d.ts +16 -0
  41. package/dist/src/cli/helpers/init/claude-settings-env.d.ts.map +1 -0
  42. package/dist/src/cli/helpers/init/claude-settings-env.js +44 -0
  43. package/dist/src/cli/helpers/init/claude-settings-env.js.map +1 -0
  44. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
  45. package/dist/src/cli/helpers/init/plugin-installer.js +9 -13
  46. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  47. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  48. package/dist/src/cli/helpers/issue-tracker/index.js +12 -6
  49. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  50. package/dist/src/cli/helpers/issue-tracker/types.d.ts +2 -0
  51. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  52. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  53. package/dist/src/core/increment/discipline-checker.js +1 -1
  54. package/dist/src/core/increment/discipline-checker.js.map +1 -1
  55. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  56. package/dist/src/core/increment/status-commands.js +7 -0
  57. package/dist/src/core/increment/status-commands.js.map +1 -1
  58. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
  59. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  60. package/dist/src/core/lazy-loading/llm-plugin-detector.js +63 -25
  61. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  62. package/dist/src/core/reflection/reflect-handler.js +2 -2
  63. package/dist/src/core/reflection/reflect-handler.js.map +1 -1
  64. package/dist/src/core/session/handoff-context.js +2 -2
  65. package/dist/src/core/session/handoff-context.js.map +1 -1
  66. package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
  67. package/dist/src/sync/ado-reconciler.js +21 -2
  68. package/dist/src/sync/ado-reconciler.js.map +1 -1
  69. package/dist/src/sync/engine.d.ts.map +1 -1
  70. package/dist/src/sync/engine.js +2 -0
  71. package/dist/src/sync/engine.js.map +1 -1
  72. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  73. package/dist/src/sync/github-reconciler.js +52 -26
  74. package/dist/src/sync/github-reconciler.js.map +1 -1
  75. package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
  76. package/dist/src/sync/jira-reconciler.js +16 -3
  77. package/dist/src/sync/jira-reconciler.js.map +1 -1
  78. package/dist/src/sync/providers/ado.d.ts.map +1 -1
  79. package/dist/src/sync/providers/ado.js +4 -2
  80. package/dist/src/sync/providers/ado.js.map +1 -1
  81. package/dist/src/sync/providers/github.d.ts.map +1 -1
  82. package/dist/src/sync/providers/github.js +11 -0
  83. package/dist/src/sync/providers/github.js.map +1 -1
  84. package/dist/src/sync/providers/jira.d.ts.map +1 -1
  85. package/dist/src/sync/providers/jira.js +14 -2
  86. package/dist/src/sync/providers/jira.js.map +1 -1
  87. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  88. package/dist/src/sync/sync-coordinator.js +31 -6
  89. package/dist/src/sync/sync-coordinator.js.map +1 -1
  90. package/dist/src/utils/auto-install.js +4 -4
  91. package/dist/src/utils/auto-install.js.map +1 -1
  92. package/package.json +2 -2
  93. package/plugins/FINAL-AUDIT-RECOMMENDATIONS.md +3 -3
  94. package/plugins/SKILLS-VS-AGENTS.md +1 -1
  95. package/plugins/specweave/PLUGIN.md +0 -2
  96. package/plugins/specweave/commands/export-skills.md +1 -1
  97. package/plugins/specweave/commands/role-orchestrator.md +1 -1
  98. package/plugins/specweave/hooks/log-decision.sh +6 -0
  99. package/plugins/specweave/hooks/stop-auto-v5.sh +17 -1
  100. package/plugins/specweave/hooks/stop-reflect.sh +16 -2
  101. package/plugins/specweave/hooks/stop-sync.sh +17 -9
  102. package/plugins/specweave/hooks/user-prompt-submit.sh +119 -35
  103. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +52 -26
  104. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  105. package/plugins/specweave/scripts/read-grill-context.sh +149 -0
  106. package/plugins/specweave/skills/code-review/SKILL.md +608 -0
  107. package/plugins/specweave/skills/done/SKILL.md +1 -1
  108. package/plugins/specweave/skills/grill/SKILL.md +91 -0
  109. package/plugins/specweave/skills/performance/SKILL.md +6 -0
  110. package/plugins/specweave/skills/security/SKILL.md +7 -0
  111. package/plugins/specweave/skills/security-patterns/SKILL.md +6 -0
  112. package/plugins/specweave/skills/tdd-orchestrator/SKILL.md +1 -1
  113. package/plugins/specweave/skills/team-build/SKILL.md +1 -1
  114. package/plugins/specweave/skills/team-orchestrate/SKILL.md +1 -1
  115. package/plugins/specweave/skills/tech-lead/SKILL.md +7 -0
  116. package/plugins/specweave-ado/lib/ado-permission-gate.js +18 -2
  117. package/plugins/specweave-ado/lib/ado-permission-gate.ts +19 -2
  118. package/plugins/specweave-frontend/skills/frontend/SKILL.md +138 -2
  119. package/plugins/specweave-frontend/skills/i18n-expert/SKILL.md +989 -0
  120. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +23 -1
  121. package/plugins/specweave-github/lib/github-feature-sync.js +41 -0
  122. package/plugins/specweave-github/lib/github-feature-sync.ts +62 -0
  123. package/plugins/specweave-infrastructure/PLUGIN.md +2 -1
  124. package/plugins/specweave-infrastructure/skills/gcp-deep-dive/SKILL.md +1172 -0
  125. package/plugins/specweave-infrastructure/skills/observability/SKILL.md +6 -0
  126. package/plugins/specweave-infrastructure/skills/opentelemetry/SKILL.md +6 -0
  127. package/plugins/specweave-jira/lib/jira-permission-gate.js +18 -2
  128. package/plugins/specweave-jira/lib/jira-permission-gate.ts +19 -2
  129. package/plugins/specweave-mobile/PLUGIN.md +1 -2
  130. package/plugins/specweave-mobile/README.md +13 -12
  131. package/plugins/specweave-mobile/skills/capacitor-ionic/SKILL.md +4 -18
  132. package/plugins/specweave-mobile/skills/deep-linking-push/SKILL.md +4 -22
  133. package/plugins/specweave-mobile/skills/expo/SKILL.md +4 -24
  134. package/plugins/specweave-mobile/skills/mobile-testing/SKILL.md +4 -22
  135. package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +404 -47
  136. package/plugins/specweave-testing/PLUGIN.md +3 -11
  137. package/plugins/specweave-testing/lib/playwright-cli-detector.js +8 -3
  138. package/plugins/specweave-testing/lib/playwright-cli-detector.ts +8 -3
  139. package/plugins/specweave-testing/lib/playwright-cli-runner.js +25 -20
  140. package/plugins/specweave-testing/lib/playwright-cli-runner.ts +24 -19
  141. package/plugins/specweave-testing/lib/playwright-routing.js +1 -6
  142. package/plugins/specweave-testing/lib/playwright-routing.ts +11 -8
  143. package/plugins/specweave-testing/skills/accessibility-testing/SKILL.md +998 -0
  144. package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +29 -28
  145. package/plugins/specweave-testing/skills/mutation-testing/SKILL.md +769 -0
  146. package/plugins/specweave-testing/skills/performance-testing/SKILL.md +961 -0
  147. package/plugins/specweave-testing/skills/qa-engineer/SKILL.md +2 -0
  148. package/plugins/specweave/.specweave/logs/decisions.jsonl +0 -12
  149. package/plugins/specweave/.specweave/logs/reflect/reflect.log +0 -8
  150. package/plugins/specweave/.specweave/logs/stop-auto.log +0 -6
  151. package/plugins/specweave/.specweave/logs/stop-sync.log +0 -10
  152. package/plugins/specweave/.specweave/state/dashboard.json +0 -43
  153. package/plugins/specweave/skills/infrastructure/SKILL.md +0 -86
  154. package/plugins/specweave/skills/qa-lead/SKILL.md +0 -77
  155. package/plugins/specweave-mobile/skills/mobile-architect/SKILL.md +0 -30
  156. package/plugins/specweave-testing/commands/e2e-setup.md +0 -1103
  157. package/plugins/specweave-testing/commands/test-coverage.md +0 -983
  158. package/plugins/specweave-testing/commands/test-generate.md +0 -1160
  159. package/plugins/specweave-testing/commands/test-init.md +0 -413
  160. package/plugins/specweave-testing/commands/ui-automate.md +0 -182
  161. package/plugins/specweave-testing/commands/ui-inspect.md +0 -82
@@ -0,0 +1,961 @@
1
+ ---
2
+ description: Performance testing expert for load testing (k6, Artillery), web performance (Lighthouse CI, Core Web Vitals), database performance, and memory/resource profiling. Use for performance tests, load tests, stress tests, lighthouse audits, k6 scripts, web vitals monitoring, or performance budgets.
3
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
4
+ model: opus
5
+ context: fork
6
+ ---
7
+
8
+ # Performance Testing Expert
9
+
10
+ ## When to Use
11
+
12
+ Invoke this skill when the task involves:
13
+ - **Load testing**: "performance test", "load test", "stress test", "soak test", "spike test"
14
+ - **Web performance**: "lighthouse", "performance budget", "bundle size", "core web vitals"
15
+ - **Specific tools**: "k6", "artillery", "lighthouse ci", "webpagetest"
16
+ - **Metrics**: "web vitals", "LCP", "FID", "INP", "CLS", "TTFB", "p95 latency"
17
+ - **Database performance**: "slow query", "n+1 query", "connection pool"
18
+ - **Resource profiling**: "memory leak", "cpu profile", "network throttle"
19
+
20
+ ## Core Expertise
21
+
22
+ - **Load testing** with k6 (primary) and Artillery
23
+ - **Web performance** with Lighthouse CI and performance budgets
24
+ - **Core Web Vitals** monitoring and optimization
25
+ - **Database performance** testing and query analysis
26
+ - **Memory and resource** leak detection and profiling
27
+
28
+ ---
29
+
30
+ ## 1. Load Testing with k6
31
+
32
+ k6 is the primary tool for HTTP load testing. It uses JavaScript ES6 modules, runs in Go for high performance, and integrates with CI/CD pipelines.
33
+
34
+ ### Installation
35
+
36
+ ```bash
37
+ # macOS
38
+ brew install k6
39
+
40
+ # Docker
41
+ docker run --rm -i grafana/k6 run - <script.js
42
+
43
+ # npm (for CI)
44
+ npm install -g k6
45
+ ```
46
+
47
+ ### Basic k6 Script
48
+
49
+ ```javascript
50
+ // load-tests/api-load.js
51
+ import http from 'k6/http';
52
+ import { check, sleep } from 'k6';
53
+
54
+ export const options = {
55
+ stages: [
56
+ { duration: '1m', target: 20 }, // ramp up to 20 VUs
57
+ { duration: '3m', target: 20 }, // hold at 20 VUs
58
+ { duration: '1m', target: 0 }, // ramp down
59
+ ],
60
+ thresholds: {
61
+ http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
62
+ http_req_failed: ['rate<0.01'], // error rate below 1%
63
+ },
64
+ };
65
+
66
+ export default function () {
67
+ const res = http.get('https://api.example.com/users');
68
+
69
+ check(res, {
70
+ 'status is 200': (r) => r.status === 200,
71
+ 'response time < 500ms': (r) => r.timings.duration < 500,
72
+ 'body has users': (r) => JSON.parse(r.body).length > 0,
73
+ });
74
+
75
+ sleep(1);
76
+ }
77
+ ```
78
+
79
+ ### k6 Scenario Patterns
80
+
81
+ #### Stress Test (Find Breaking Point)
82
+
83
+ ```javascript
84
+ export const options = {
85
+ scenarios: {
86
+ stress: {
87
+ executor: 'ramping-vus',
88
+ startVUs: 0,
89
+ stages: [
90
+ { duration: '2m', target: 100 },
91
+ { duration: '5m', target: 100 },
92
+ { duration: '2m', target: 200 },
93
+ { duration: '5m', target: 200 },
94
+ { duration: '2m', target: 300 },
95
+ { duration: '5m', target: 300 },
96
+ { duration: '5m', target: 0 },
97
+ ],
98
+ },
99
+ },
100
+ thresholds: {
101
+ http_req_duration: ['p(99)<1500'],
102
+ http_req_failed: ['rate<0.05'],
103
+ },
104
+ };
105
+ ```
106
+
107
+ #### Spike Test (Sudden Burst)
108
+
109
+ ```javascript
110
+ export const options = {
111
+ scenarios: {
112
+ spike: {
113
+ executor: 'ramping-vus',
114
+ startVUs: 0,
115
+ stages: [
116
+ { duration: '30s', target: 10 }, // warm up
117
+ { duration: '10s', target: 500 }, // spike
118
+ { duration: '1m', target: 500 }, // hold spike
119
+ { duration: '10s', target: 10 }, // drop
120
+ { duration: '2m', target: 10 }, // recovery
121
+ { duration: '30s', target: 0 }, // ramp down
122
+ ],
123
+ },
124
+ },
125
+ };
126
+ ```
127
+
128
+ #### Soak Test (Extended Duration)
129
+
130
+ ```javascript
131
+ export const options = {
132
+ scenarios: {
133
+ soak: {
134
+ executor: 'constant-vus',
135
+ vus: 50,
136
+ duration: '4h',
137
+ },
138
+ },
139
+ thresholds: {
140
+ http_req_duration: ['p(95)<800', 'p(99)<1500'],
141
+ http_req_failed: ['rate<0.01'],
142
+ },
143
+ };
144
+ ```
145
+
146
+ #### Constant Request Rate
147
+
148
+ ```javascript
149
+ export const options = {
150
+ scenarios: {
151
+ constant_rate: {
152
+ executor: 'constant-arrival-rate',
153
+ rate: 100, // 100 requests per timeUnit
154
+ timeUnit: '1s',
155
+ duration: '5m',
156
+ preAllocatedVUs: 50,
157
+ maxVUs: 200,
158
+ },
159
+ },
160
+ };
161
+ ```
162
+
163
+ ### k6 Custom Metrics
164
+
165
+ ```javascript
166
+ import { Counter, Trend, Rate, Gauge } from 'k6/metrics';
167
+
168
+ const orderDuration = new Trend('order_duration');
169
+ const orderErrors = new Rate('order_errors');
170
+ const ordersCreated = new Counter('orders_created');
171
+
172
+ export default function () {
173
+ const start = Date.now();
174
+ const res = http.post('https://api.example.com/orders', JSON.stringify({
175
+ product: 'widget', quantity: 1,
176
+ }), { headers: { 'Content-Type': 'application/json' } });
177
+
178
+ orderDuration.add(Date.now() - start);
179
+ orderErrors.add(res.status !== 201);
180
+ if (res.status === 201) ordersCreated.add(1);
181
+ }
182
+ ```
183
+
184
+ ### k6 Authentication and Headers
185
+
186
+ ```javascript
187
+ import http from 'k6/http';
188
+ import { check } from 'k6';
189
+
190
+ export function setup() {
191
+ const loginRes = http.post('https://api.example.com/auth/login', JSON.stringify({
192
+ username: 'loadtest',
193
+ password: __ENV.LOAD_TEST_PASSWORD,
194
+ }), { headers: { 'Content-Type': 'application/json' } });
195
+
196
+ return { token: JSON.parse(loginRes.body).token };
197
+ }
198
+
199
+ export default function (data) {
200
+ const params = {
201
+ headers: {
202
+ Authorization: `Bearer ${data.token}`,
203
+ 'Content-Type': 'application/json',
204
+ },
205
+ };
206
+ const res = http.get('https://api.example.com/protected', params);
207
+ check(res, { 'authenticated request OK': (r) => r.status === 200 });
208
+ }
209
+ ```
210
+
211
+ ### k6 Multi-Scenario (Mixed Workload)
212
+
213
+ ```javascript
214
+ export const options = {
215
+ scenarios: {
216
+ browse: {
217
+ executor: 'constant-vus', vus: 30, duration: '10m',
218
+ exec: 'browseProducts',
219
+ },
220
+ checkout: {
221
+ executor: 'ramping-arrival-rate', startRate: 5, timeUnit: '1s',
222
+ stages: [{ duration: '5m', target: 20 }, { duration: '5m', target: 20 }],
223
+ preAllocatedVUs: 50, exec: 'checkout',
224
+ },
225
+ },
226
+ thresholds: {
227
+ 'http_req_duration{scenario:browse}': ['p(95)<300'],
228
+ 'http_req_duration{scenario:checkout}': ['p(95)<1000'],
229
+ },
230
+ };
231
+
232
+ export function browseProducts() {
233
+ http.get('https://api.example.com/products');
234
+ sleep(Math.random() * 3 + 1);
235
+ }
236
+
237
+ export function checkout() {
238
+ http.post('https://api.example.com/checkout', JSON.stringify({
239
+ items: [{ id: 1, qty: 1 }],
240
+ }), { headers: { 'Content-Type': 'application/json' } });
241
+ }
242
+ ```
243
+
244
+ ### Running k6
245
+
246
+ ```bash
247
+ k6 run load-tests/api-load.js # Basic run
248
+ k6 run -e BASE_URL=https://staging.example.com load-tests/api-load.js # Env vars
249
+ k6 run --vus 100 --duration 5m load-tests/api-load.js # Override options
250
+ k6 run --out json=results.json load-tests/api-load.js # JSON output
251
+ k6 run --out influxdb=http://localhost:8086/k6 load-tests/api-load.js # Grafana
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 2. Load Testing with Artillery
257
+
258
+ Artillery uses YAML configuration and is well-suited for quick HTTP and WebSocket load tests.
259
+
260
+ ### Artillery Configuration
261
+
262
+ ```yaml
263
+ # load-tests/artillery.yml
264
+ config:
265
+ target: "https://api.example.com"
266
+ phases:
267
+ - duration: 60
268
+ arrivalRate: 5
269
+ name: "Warm up"
270
+ - duration: 120
271
+ arrivalRate: 20
272
+ rampTo: 50
273
+ name: "Ramp up"
274
+ - duration: 300
275
+ arrivalRate: 50
276
+ name: "Sustained load"
277
+ defaults:
278
+ headers:
279
+ Content-Type: "application/json"
280
+ ensure:
281
+ p95: 500
282
+ maxErrorRate: 1
283
+
284
+ scenarios:
285
+ - name: "Browse and Purchase"
286
+ weight: 70
287
+ flow:
288
+ - get:
289
+ url: "/products"
290
+ capture:
291
+ - json: "$[0].id"
292
+ as: "productId"
293
+ - think: 2
294
+ - get:
295
+ url: "/products/{{ productId }}"
296
+ - post:
297
+ url: "/cart"
298
+ json:
299
+ productId: "{{ productId }}"
300
+ quantity: 1
301
+
302
+ - name: "Search"
303
+ weight: 30
304
+ flow:
305
+ - get:
306
+ url: "/search?q=widget"
307
+ - think: 3
308
+ ```
309
+
310
+ ### Running Artillery
311
+
312
+ ```bash
313
+ npx artillery run load-tests/artillery.yml # Run test
314
+ npx artillery run --output report.json load-tests/artillery.yml # Save results
315
+ npx artillery report report.json # HTML report
316
+ npx artillery quick --count 50 --num 10 https://api.example.com/health # Quick test
317
+ ```
318
+
319
+ ---
320
+
321
+ ## 3. Web Performance with Lighthouse CI
322
+
323
+ ### Lighthouse CI Configuration
324
+
325
+ ```javascript
326
+ // lighthouserc.js
327
+ module.exports = {
328
+ ci: {
329
+ collect: {
330
+ url: [
331
+ 'http://localhost:3000/',
332
+ 'http://localhost:3000/products',
333
+ 'http://localhost:3000/checkout',
334
+ ],
335
+ startServerCommand: 'npm run start',
336
+ startServerReadyPattern: 'Server started',
337
+ numberOfRuns: 3,
338
+ settings: { preset: 'desktop' },
339
+ },
340
+ assert: {
341
+ assertions: {
342
+ 'categories:performance': ['error', { minScore: 0.9 }],
343
+ 'categories:accessibility': ['warn', { minScore: 0.9 }],
344
+ 'categories:best-practices': ['warn', { minScore: 0.9 }],
345
+
346
+ // Core Web Vitals
347
+ 'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
348
+ 'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
349
+ 'total-blocking-time': ['error', { maxNumericValue: 300 }],
350
+
351
+ // Bundle and resource budgets
352
+ 'resource-summary:script:size': ['error', { maxNumericValue: 300000 }],
353
+ 'resource-summary:total:size': ['error', { maxNumericValue: 800000 }],
354
+ 'resource-summary:third-party:count': ['warn', { maxNumericValue: 10 }],
355
+ },
356
+ },
357
+ upload: {
358
+ target: 'temporary-public-storage',
359
+ },
360
+ },
361
+ };
362
+ ```
363
+
364
+ ### Performance Budgets
365
+
366
+ ```json
367
+ [
368
+ {
369
+ "path": "/*",
370
+ "timings": [
371
+ { "metric": "interactive", "budget": 3000 },
372
+ { "metric": "first-contentful-paint", "budget": 1500 },
373
+ { "metric": "largest-contentful-paint", "budget": 2500 },
374
+ { "metric": "cumulative-layout-shift", "budget": 0.1 },
375
+ { "metric": "total-blocking-time", "budget": 300 }
376
+ ],
377
+ "resourceSizes": [
378
+ { "resourceType": "script", "budget": 300 },
379
+ { "resourceType": "stylesheet", "budget": 100 },
380
+ { "resourceType": "image", "budget": 500 },
381
+ { "resourceType": "total", "budget": 1000 }
382
+ ],
383
+ "resourceCounts": [
384
+ { "resourceType": "script", "budget": 15 },
385
+ { "resourceType": "third-party", "budget": 10 },
386
+ { "resourceType": "total", "budget": 50 }
387
+ ]
388
+ }
389
+ ]
390
+ ```
391
+
392
+ ### Lighthouse CI in GitHub Actions
393
+
394
+ ```yaml
395
+ # .github/workflows/lighthouse.yml
396
+ name: Lighthouse CI
397
+ on: [pull_request]
398
+
399
+ jobs:
400
+ lighthouse:
401
+ runs-on: ubuntu-latest
402
+ steps:
403
+ - uses: actions/checkout@v4
404
+ - uses: actions/setup-node@v4
405
+ with:
406
+ node-version: 20
407
+ - run: npm ci && npm run build
408
+ - name: Run Lighthouse CI
409
+ run: |
410
+ npm install -g @lhci/cli
411
+ lhci autorun
412
+ env:
413
+ LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
414
+ - name: Upload Lighthouse Report
415
+ if: always()
416
+ uses: actions/upload-artifact@v4
417
+ with:
418
+ name: lighthouse-report
419
+ path: .lighthouseci/
420
+ ```
421
+
422
+ ### Lighthouse Programmatic API
423
+
424
+ ```typescript
425
+ import lighthouse from 'lighthouse';
426
+ import * as chromeLauncher from 'chrome-launcher';
427
+
428
+ async function runLighthouse(url: string) {
429
+ const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
430
+ const result = await lighthouse(url, {
431
+ port: chrome.port, output: 'json', onlyCategories: ['performance'],
432
+ });
433
+
434
+ const { categories, audits } = result.lhr;
435
+ console.log('Performance Score:', categories.performance.score * 100);
436
+ console.log('LCP:', audits['largest-contentful-paint'].displayValue);
437
+ console.log('TBT:', audits['total-blocking-time'].displayValue);
438
+ console.log('CLS:', audits['cumulative-layout-shift'].displayValue);
439
+
440
+ if (categories.performance.score < 0.9) {
441
+ console.error('Performance score below 90!');
442
+ process.exit(1);
443
+ }
444
+ await chrome.kill();
445
+ }
446
+ ```
447
+
448
+ ---
449
+
450
+ ## 4. Core Web Vitals Monitoring
451
+
452
+ ### Thresholds
453
+
454
+ | Metric | Good | Needs Improvement | Poor |
455
+ |--------|------|-------------------|------|
456
+ | **LCP** (Largest Contentful Paint) | <= 2.5s | <= 4.0s | > 4.0s |
457
+ | **INP** (Interaction to Next Paint) | <= 200ms | <= 500ms | > 500ms |
458
+ | **CLS** (Cumulative Layout Shift) | <= 0.1 | <= 0.25 | > 0.25 |
459
+ | **FCP** (First Contentful Paint) | <= 1.8s | <= 3.0s | > 3.0s |
460
+ | **TTFB** (Time to First Byte) | <= 800ms | <= 1800ms | > 1800ms |
461
+
462
+ ### PerformanceObserver API
463
+
464
+ ```typescript
465
+ // LCP Observer
466
+ function observeLCP(callback: (value: number) => void) {
467
+ const observer = new PerformanceObserver((list) => {
468
+ const entries = list.getEntries();
469
+ callback(entries[entries.length - 1].startTime);
470
+ });
471
+ observer.observe({ type: 'largest-contentful-paint', buffered: true });
472
+ }
473
+
474
+ // INP Observer (replaces FID)
475
+ function observeINP(callback: (value: number) => void) {
476
+ let maxDuration = 0;
477
+ const observer = new PerformanceObserver((list) => {
478
+ for (const entry of list.getEntries()) {
479
+ const inp = (entry as PerformanceEventTiming).duration;
480
+ if (inp > maxDuration) { maxDuration = inp; callback(inp); }
481
+ }
482
+ });
483
+ observer.observe({ type: 'event', buffered: true });
484
+ }
485
+
486
+ // CLS Observer
487
+ function observeCLS(callback: (value: number) => void) {
488
+ let clsValue = 0, sessionValue = 0;
489
+ let sessionEntries: PerformanceEntry[] = [];
490
+ const observer = new PerformanceObserver((list) => {
491
+ for (const entry of list.getEntries() as any[]) {
492
+ if (!entry.hadRecentInput) {
493
+ const first = sessionEntries[0], last = sessionEntries[sessionEntries.length - 1];
494
+ if (sessionValue && entry.startTime - last?.startTime < 1000
495
+ && entry.startTime - first?.startTime < 5000) {
496
+ sessionValue += entry.value;
497
+ sessionEntries.push(entry);
498
+ } else {
499
+ sessionValue = entry.value;
500
+ sessionEntries = [entry];
501
+ }
502
+ if (sessionValue > clsValue) { clsValue = sessionValue; callback(clsValue); }
503
+ }
504
+ }
505
+ });
506
+ observer.observe({ type: 'layout-shift', buffered: true });
507
+ }
508
+ ```
509
+
510
+ ### Using the web-vitals Library
511
+
512
+ ```typescript
513
+ import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';
514
+
515
+ function sendToAnalytics(metric: { name: string; value: number; rating: string; id: string }) {
516
+ fetch('/api/vitals', {
517
+ method: 'POST',
518
+ headers: { 'Content-Type': 'application/json' },
519
+ body: JSON.stringify({
520
+ name: metric.name, value: metric.value, rating: metric.rating,
521
+ url: window.location.href, timestamp: Date.now(),
522
+ }),
523
+ keepalive: true,
524
+ });
525
+ }
526
+
527
+ export function initVitalsReporting() {
528
+ onLCP(sendToAnalytics);
529
+ onINP(sendToAnalytics);
530
+ onCLS(sendToAnalytics);
531
+ onFCP(sendToAnalytics);
532
+ onTTFB(sendToAnalytics);
533
+ }
534
+ ```
535
+
536
+ ### Real User Monitoring (RUM) Setup
537
+
538
+ ```typescript
539
+ class RealUserMonitoring {
540
+ constructor(private config: { endpoint: string; sampleRate: number; appVersion: string }) {}
541
+
542
+ init() {
543
+ if (Math.random() > this.config.sampleRate) return;
544
+ this.observeNavigation();
545
+ this.observeResources();
546
+ this.observeLongTasks();
547
+ }
548
+
549
+ private observeNavigation() {
550
+ new PerformanceObserver((list) => {
551
+ for (const entry of list.getEntries()) {
552
+ const nav = entry as PerformanceNavigationTiming;
553
+ this.report('navigation', {
554
+ ttfb: nav.responseStart - nav.requestStart,
555
+ domComplete: nav.domComplete - nav.domInteractive,
556
+ totalTime: nav.loadEventEnd - nav.startTime,
557
+ });
558
+ }
559
+ }).observe({ type: 'navigation', buffered: true });
560
+ }
561
+
562
+ private observeResources() {
563
+ new PerformanceObserver((list) => {
564
+ for (const entry of list.getEntries()) {
565
+ const res = entry as PerformanceResourceTiming;
566
+ if (res.duration > 1000) {
567
+ this.report('slow-resource', {
568
+ name: res.name, duration: res.duration, size: res.transferSize,
569
+ });
570
+ }
571
+ }
572
+ }).observe({ type: 'resource', buffered: true });
573
+ }
574
+
575
+ private observeLongTasks() {
576
+ new PerformanceObserver((list) => {
577
+ for (const entry of list.getEntries()) {
578
+ this.report('long-task', { duration: entry.duration, startTime: entry.startTime });
579
+ }
580
+ }).observe({ type: 'longtask', buffered: true });
581
+ }
582
+
583
+ private report(type: string, data: Record<string, unknown>) {
584
+ fetch(this.config.endpoint, {
585
+ method: 'POST',
586
+ headers: { 'Content-Type': 'application/json' },
587
+ body: JSON.stringify({
588
+ type, data, appVersion: this.config.appVersion,
589
+ url: window.location.href, timestamp: Date.now(),
590
+ }),
591
+ keepalive: true,
592
+ }).catch(() => { /* RUM should never break the app */ });
593
+ }
594
+ }
595
+
596
+ // Usage: sample 10% of users
597
+ const rum = new RealUserMonitoring({ endpoint: '/api/rum', sampleRate: 0.1, appVersion: '1.2.3' });
598
+ rum.init();
599
+ ```
600
+
601
+ ---
602
+
603
+ ## 5. Database Performance Testing
604
+
605
+ ### Slow Query Detection
606
+
607
+ ```typescript
608
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
609
+ import { Pool } from 'pg';
610
+
611
+ describe('Database Query Performance', () => {
612
+ let pool: Pool;
613
+
614
+ beforeAll(async () => { pool = new Pool({ connectionString: process.env.DATABASE_URL }); });
615
+ afterAll(async () => { await pool.end(); });
616
+
617
+ it('should fetch user by ID within 50ms', async () => {
618
+ const start = performance.now();
619
+ await pool.query('SELECT * FROM users WHERE id = $1', [1]);
620
+ expect(performance.now() - start).toBeLessThan(50);
621
+ });
622
+
623
+ it('should detect missing indexes via EXPLAIN ANALYZE', async () => {
624
+ const result = await pool.query(
625
+ "EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_email = 'test@example.com'"
626
+ );
627
+ const plan = result.rows.map((r) => r['QUERY PLAN']).join('\n');
628
+ // Should use index scan, not sequential scan on large tables
629
+ expect(plan).not.toMatch(/Seq Scan.*orders/);
630
+ });
631
+ });
632
+ ```
633
+
634
+ ### Connection Pool Testing
635
+
636
+ ```typescript
637
+ describe('Connection Pool Behavior', () => {
638
+ it('should handle concurrent connections within pool limits', async () => {
639
+ const pool = new Pool({
640
+ connectionString: process.env.DATABASE_URL,
641
+ max: 10, idleTimeoutMillis: 5000, connectionTimeoutMillis: 3000,
642
+ });
643
+
644
+ const concurrentQueries = 20;
645
+ const start = performance.now();
646
+ const results = await Promise.allSettled(
647
+ Array.from({ length: concurrentQueries }, () => pool.query('SELECT pg_sleep(0.1)'))
648
+ );
649
+ const duration = performance.now() - start;
650
+
651
+ expect(results.filter((r) => r.status === 'fulfilled').length).toBe(concurrentQueries);
652
+ // 10 pool connections, 20 queries at 100ms: ~200ms (2 batches), not 2000ms
653
+ expect(duration).toBeLessThan(1000);
654
+ await pool.end();
655
+ });
656
+
657
+ it('should timeout when pool is exhausted', async () => {
658
+ const pool = new Pool({
659
+ connectionString: process.env.DATABASE_URL, max: 2, connectionTimeoutMillis: 500,
660
+ });
661
+ const held = await Promise.all([pool.connect(), pool.connect()]);
662
+ await expect(pool.connect()).rejects.toThrow(/timeout/);
663
+ for (const client of held) client.release();
664
+ await pool.end();
665
+ });
666
+ });
667
+ ```
668
+
669
+ ### N+1 Query Detection
670
+
671
+ ```typescript
672
+ class QueryCounter {
673
+ queries: string[] = [];
674
+ private originalQuery: Function;
675
+
676
+ constructor(private pool: any) { this.originalQuery = pool.query.bind(pool); }
677
+
678
+ start() {
679
+ this.queries = [];
680
+ this.pool.query = (...args: any[]) => { this.queries.push(args[0]); return this.originalQuery(...args); };
681
+ }
682
+
683
+ stop() { this.pool.query = this.originalQuery; }
684
+ get count() { return this.queries.length; }
685
+ }
686
+
687
+ describe('N+1 Query Detection', () => {
688
+ it('should fetch orders with items in constant queries (not N+1)', async () => {
689
+ const counter = new QueryCounter(pool);
690
+ counter.start();
691
+ const orders = await orderService.getOrdersWithItems({ limit: 50 });
692
+ counter.stop();
693
+
694
+ // Expect at most 2-3 queries (orders + items batch), not 51 (1 + N)
695
+ expect(counter.count).toBeLessThanOrEqual(3);
696
+ expect(orders.length).toBeGreaterThan(0);
697
+ });
698
+ });
699
+ ```
700
+
701
+ ---
702
+
703
+ ## 6. Memory and Resource Testing
704
+
705
+ ### Memory Leak Detection in Node.js
706
+
707
+ ```typescript
708
+ describe('Memory Leak Detection', () => {
709
+ it('should not leak memory during repeated operations', async () => {
710
+ if (global.gc) global.gc(); // Run with --expose-gc
711
+ const baselineMemory = process.memoryUsage().heapUsed;
712
+ const iterations = 1000;
713
+
714
+ for (let i = 0; i < iterations; i++) {
715
+ await processRequest({ id: i, data: 'test' });
716
+ }
717
+
718
+ if (global.gc) global.gc();
719
+ const growthPerIteration = (process.memoryUsage().heapUsed - baselineMemory) / iterations;
720
+ expect(growthPerIteration).toBeLessThan(1024); // < 1KB per iteration
721
+ });
722
+
723
+ it('should properly clean up event listeners', () => {
724
+ const emitter = new EventEmitter();
725
+ const initialListeners = emitter.listenerCount('data');
726
+
727
+ for (let i = 0; i < 100; i++) {
728
+ const service = new DataService(emitter);
729
+ service.start();
730
+ service.stop(); // Must remove listeners
731
+ }
732
+ expect(emitter.listenerCount('data')).toBe(initialListeners);
733
+ });
734
+ });
735
+ ```
736
+
737
+ ### Browser Memory Profiling (Playwright)
738
+
739
+ ```typescript
740
+ import { test, expect } from '@playwright/test';
741
+
742
+ test('should not leak memory on repeated modal open/close', async ({ page }) => {
743
+ await page.goto('/dashboard');
744
+ const getMemory = () => page.evaluate(() => (performance as any).memory?.usedJSHeapSize || 0);
745
+ const initialMemory = await getMemory();
746
+
747
+ for (let i = 0; i < 20; i++) {
748
+ await page.click('[data-testid="open-modal"]');
749
+ await page.waitForSelector('[data-testid="modal"]');
750
+ await page.click('[data-testid="close-modal"]');
751
+ await page.waitForSelector('[data-testid="modal"]', { state: 'hidden' });
752
+ }
753
+
754
+ await page.evaluate(() => { if ((window as any).gc) (window as any).gc(); });
755
+ expect(await getMemory() - initialMemory).toBeLessThan(5 * 1024 * 1024); // < 5MB growth
756
+ });
757
+ ```
758
+
759
+ ### CPU Profiling
760
+
761
+ ```typescript
762
+ import { Session } from 'inspector';
763
+ import { writeFileSync } from 'fs';
764
+
765
+ async function profileOperation(operation: () => Promise<void>) {
766
+ const session = new Session();
767
+ session.connect();
768
+ session.post('Profiler.enable');
769
+ session.post('Profiler.start');
770
+
771
+ await operation();
772
+
773
+ return new Promise<void>((resolve) => {
774
+ session.post('Profiler.stop', (err, { profile }) => {
775
+ if (!err) writeFileSync('cpu-profile.cpuprofile', JSON.stringify(profile));
776
+ session.disconnect();
777
+ resolve();
778
+ });
779
+ });
780
+ }
781
+ ```
782
+
783
+ ### Network Throttling Test Strategies
784
+
785
+ ```typescript
786
+ import { test, expect } from '@playwright/test';
787
+
788
+ const networkConditions = {
789
+ 'slow-3g': { downloadThroughput: 50_000, uploadThroughput: 25_000, latency: 400 },
790
+ 'fast-3g': { downloadThroughput: 187_500, uploadThroughput: 75_000, latency: 150 },
791
+ 'regular-4g': { downloadThroughput: 500_000, uploadThroughput: 250_000, latency: 50 },
792
+ };
793
+
794
+ const budgets: Record<string, number> = { 'slow-3g': 3000, 'fast-3g': 1500, 'regular-4g': 800 };
795
+
796
+ for (const [name, conditions] of Object.entries(networkConditions)) {
797
+ test(`page loads within budget on ${name}`, async ({ page }) => {
798
+ const cdp = await page.context().newCDPSession(page);
799
+ await cdp.send('Network.emulateNetworkConditions', { offline: false, ...conditions });
800
+
801
+ const start = Date.now();
802
+ await page.goto('/', { waitUntil: 'domcontentloaded' });
803
+ expect(Date.now() - start).toBeLessThan(budgets[name]);
804
+ });
805
+ }
806
+ ```
807
+
808
+ ### Bundle Size Budget Tests
809
+
810
+ ```typescript
811
+ import { describe, it, expect } from 'vitest';
812
+ import { statSync, readdirSync } from 'fs';
813
+ import { join } from 'path';
814
+
815
+ describe('Bundle Size Budget', () => {
816
+ const distDir = join(process.cwd(), 'dist');
817
+
818
+ function totalSize(dir: string, ext: string): number {
819
+ const files = readdirSync(dir, { recursive: true }) as string[];
820
+ return files
821
+ .filter((f) => f.endsWith(ext))
822
+ .reduce((sum, f) => sum + statSync(join(dir, f)).size, 0);
823
+ }
824
+
825
+ it('total JS bundle should be under 300KB', () => {
826
+ expect(totalSize(distDir, '.js')).toBeLessThan(300 * 1024);
827
+ });
828
+
829
+ it('total CSS should be under 100KB', () => {
830
+ expect(totalSize(distDir, '.css')).toBeLessThan(100 * 1024);
831
+ });
832
+ });
833
+ ```
834
+
835
+ ---
836
+
837
+ ## 7. CI/CD Performance Regression Detection
838
+
839
+ ### Performance Gate in GitHub Actions
840
+
841
+ ```yaml
842
+ # .github/workflows/perf-check.yml
843
+ name: Performance Check
844
+ on: [pull_request]
845
+
846
+ jobs:
847
+ load-test:
848
+ runs-on: ubuntu-latest
849
+ steps:
850
+ - uses: actions/checkout@v4
851
+ - uses: actions/setup-node@v4
852
+ with:
853
+ node-version: 20
854
+ - run: npm ci && npm run build
855
+ - name: Start server
856
+ run: npm start &
857
+ env:
858
+ NODE_ENV: production
859
+ - run: npx wait-on http://localhost:3000 --timeout 30000
860
+ - name: Run k6 load test
861
+ uses: grafana/k6-action@v0.3.1
862
+ with:
863
+ filename: load-tests/api-load.js
864
+ flags: --out json=k6-results.json
865
+ - name: Upload results
866
+ if: always()
867
+ uses: actions/upload-artifact@v4
868
+ with:
869
+ name: k6-results
870
+ path: k6-results.json
871
+ ```
872
+
873
+ ### Performance Comparison Script
874
+
875
+ ```typescript
876
+ interface PerfMetrics { p50: number; p95: number; p99: number; errorRate: number; throughput: number; }
877
+
878
+ function compareMetrics(base: PerfMetrics, current: PerfMetrics) {
879
+ const regressions: string[] = [];
880
+ const threshold = 0.10; // 10% regression threshold
881
+
882
+ if (current.p95 > base.p95 * (1 + threshold))
883
+ regressions.push(`p95 regressed: ${base.p95.toFixed(0)}ms -> ${current.p95.toFixed(0)}ms`);
884
+ if (current.errorRate > base.errorRate * (1 + threshold) && current.errorRate > 0.01)
885
+ regressions.push(`Error rate regressed: ${(base.errorRate*100).toFixed(2)}% -> ${(current.errorRate*100).toFixed(2)}%`);
886
+ if (current.throughput < base.throughput * (1 - threshold))
887
+ regressions.push(`Throughput regressed: ${base.throughput.toFixed(0)} -> ${current.throughput.toFixed(0)} rps`);
888
+
889
+ return { passed: regressions.length === 0, regressions };
890
+ }
891
+ ```
892
+
893
+ ---
894
+
895
+ ## 8. Quick Reference
896
+
897
+ ### CLI Commands
898
+
899
+ ```bash
900
+ # k6
901
+ k6 run script.js # Run load test
902
+ k6 run --vus 50 --duration 2m # Override options
903
+ k6 run -e API_KEY=xxx script.js # Pass env vars
904
+ k6 run --out json=out.json # JSON output
905
+ k6 inspect script.js # Validate script
906
+
907
+ # Lighthouse CI
908
+ lhci autorun # Full CI pipeline
909
+ lhci collect --url=http://... # Collect only
910
+ lhci assert # Assert against config
911
+ lhci upload # Upload results
912
+ ```
913
+
914
+ ### Performance Testing Decision Matrix
915
+
916
+ | Scenario | Tool | Configuration |
917
+ |----------|------|---------------|
918
+ | API load testing | k6 | ramping-vus, thresholds |
919
+ | Stress/breaking point | k6 | ramping-vus with high targets |
920
+ | Soak testing | k6 | constant-vus, long duration |
921
+ | Spike testing | k6 | ramping-vus, sudden jumps |
922
+ | Web performance audit | Lighthouse CI | assert with budgets |
923
+ | Bundle size regression | Vitest | statSync + budget checks |
924
+ | Core Web Vitals (lab) | Lighthouse CI | performance category |
925
+ | Core Web Vitals (field) | web-vitals lib | RUM endpoint |
926
+ | Database queries | Vitest + pg | EXPLAIN ANALYZE |
927
+ | Memory leaks | Node inspector | heap snapshots |
928
+ | Network conditions | Playwright CDP | emulateNetworkConditions |
929
+
930
+ ### Threshold Guidelines
931
+
932
+ | Metric | Target | Maximum |
933
+ |--------|--------|---------|
934
+ | API p95 latency | < 200ms | < 500ms |
935
+ | API p99 latency | < 500ms | < 1500ms |
936
+ | Error rate | < 0.1% | < 1% |
937
+ | LCP | < 2.5s | < 4.0s |
938
+ | INP | < 200ms | < 500ms |
939
+ | CLS | < 0.1 | < 0.25 |
940
+ | JS bundle (total) | < 200KB | < 300KB |
941
+ | CSS (total) | < 50KB | < 100KB |
942
+ | DB query | < 50ms | < 200ms |
943
+
944
+ ## Best Practices
945
+
946
+ 1. **Test in production-like environments** -- staging with similar data volumes and infra
947
+ 2. **Establish baselines** before optimizing -- measure first, then improve
948
+ 3. **Set realistic thresholds** -- based on SLAs and user expectations, not arbitrary numbers
949
+ 4. **Automate in CI/CD** -- performance regressions should block merges
950
+ 5. **Monitor real users (RUM)** -- lab data is a proxy; field data is truth
951
+ 6. **Test at scale** -- seed databases with production-like data volumes
952
+ 7. **Profile before optimizing** -- find the bottleneck, do not guess
953
+ 8. **Budget for third parties** -- third-party scripts are the top performance killer
954
+ 9. **Test degraded conditions** -- slow networks, cold caches, high concurrency
955
+ 10. **Track trends over time** -- individual runs have variance; trends reveal regressions
956
+
957
+ ## Related Skills
958
+
959
+ - `qa-engineer` - Overall test strategy and quality gates
960
+ - `e2e-testing` - Browser automation and E2E tests
961
+ - `unit-testing` - Unit tests and TDD workflow