@avi770/testteam 1.2.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.
Files changed (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. package/package.json +88 -0
@@ -0,0 +1,411 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Stub config
7
+ // ---------------------------------------------------------------------------
8
+
9
+ const stubConfig = {
10
+ schemaVersion: 1,
11
+ name: 'test',
12
+ auth: {
13
+ loginUrl: 'http://localhost/login',
14
+ credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
15
+ },
16
+ modules: [
17
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
18
+ { id: 'reports', route: '/reports', sidebarIcon: true },
19
+ ],
20
+ environments: {
21
+ beta: { baseUrl: 'http://localhost:3000', seed: false },
22
+ },
23
+ portals: [],
24
+ breakpoints: [],
25
+ integrations: [],
26
+ workers: [],
27
+ workflows: [],
28
+ accuracy: { enabled: true, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
29
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
30
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
31
+ agentClassification: { blocking: [], advisory: [] },
32
+ thresholds: {
33
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
34
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
35
+ apiResponseTime: 2000,
36
+ bundleSizeLimit: 500000,
37
+ maxConsoleErrors: 0,
38
+ },
39
+ disabledAgents: [],
40
+ disabledAgentSet: new Set<number>(),
41
+ } satisfies ValidatedConfig;
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Mock fetch (used in preFlight isUrlReachable)
45
+ // ---------------------------------------------------------------------------
46
+
47
+ const fetchMock = vi.fn();
48
+ vi.stubGlobal('fetch', fetchMock);
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Mock navigation helpers
52
+ // ---------------------------------------------------------------------------
53
+
54
+ const loginMock = vi.fn().mockResolvedValue(undefined);
55
+ const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
56
+
57
+ vi.mock('../../helpers/navigation', () => ({
58
+ login: (...args: unknown[]) => loginMock(...args),
59
+ navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
60
+ }));
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Mock Playwright page
64
+ // ---------------------------------------------------------------------------
65
+
66
+ interface MockPage {
67
+ close: ReturnType<typeof vi.fn>;
68
+ evaluate: ReturnType<typeof vi.fn>;
69
+ }
70
+
71
+ /** Default evaluate returns: [navEntries=[], paintEntries=[], lcpMs=0, heapSize=0] */
72
+ function makeMockPage(evaluateResponses: unknown[] = []): MockPage {
73
+ let callIndex = 0;
74
+ return {
75
+ close: vi.fn().mockResolvedValue(undefined),
76
+ evaluate: vi.fn().mockImplementation(async () => {
77
+ const val = evaluateResponses[callIndex] ?? 0;
78
+ callIndex++;
79
+ return val;
80
+ }),
81
+ };
82
+ }
83
+
84
+ let currentMockPage: MockPage;
85
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
86
+
87
+ vi.mock('playwright', () => ({
88
+ chromium: {
89
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
90
+ },
91
+ }));
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Mock lighthouse (optional peer dep) — disabled by default
95
+ // ---------------------------------------------------------------------------
96
+
97
+ vi.mock('lighthouse', () => {
98
+ throw new Error('lighthouse not installed');
99
+ });
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Dynamic import AFTER mocks
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const { PerformanceProfilerAgent } = await import('../13-performance-profiler');
106
+
107
+ function createAgent(phase: Phase = 'beta'): InstanceType<typeof PerformanceProfilerAgent> {
108
+ return new PerformanceProfilerAgent(stubConfig, phase, '/tmp/test-run');
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Tests
113
+ // ---------------------------------------------------------------------------
114
+
115
+ describe('PerformanceProfilerAgent', () => {
116
+ beforeEach(() => {
117
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
118
+ loginMock.mockReset().mockResolvedValue(undefined);
119
+ navigateToSectionMock.mockReset().mockResolvedValue(undefined);
120
+
121
+ // Default page: evaluate returns correct types per call order.
122
+ // Per module (2 modules): loadTime:number, paintEntries:array, lcpMs:number
123
+ // After all modules: heap:number
124
+ currentMockPage = makeMockPage([
125
+ // dashboard
126
+ 0, [], 0,
127
+ // reports
128
+ 0, [], 0,
129
+ // heap (memory check)
130
+ 0,
131
+ ]);
132
+
133
+ mockBrowser = {
134
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
135
+ close: vi.fn().mockResolvedValue(undefined),
136
+ };
137
+ });
138
+
139
+ it('has agentId 13 and agentName "Performance Profiler"', () => {
140
+ const agent = createAgent();
141
+ expect(agent.agentId).toBe(13);
142
+ expect(agent.agentName).toBe('Performance Profiler');
143
+ });
144
+
145
+ // -------------------------------------------------------------------------
146
+ // preFlight()
147
+ // -------------------------------------------------------------------------
148
+
149
+ describe('preFlight()', () => {
150
+ it('resolves gracefully when base URL is not reachable (fallback)', async () => {
151
+ fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
152
+
153
+ const agent = createAgent();
154
+ const result = await agent.run();
155
+
156
+ // With resolveEnvironment the agent no longer throws on unreachable URLs.
157
+ expect(result.status).toBe('passed');
158
+ });
159
+
160
+ it('passes when base URL is reachable', async () => {
161
+ const agent = createAgent();
162
+ const result = await agent.run();
163
+
164
+ expect(result.findings.some((f) => f.description.includes('not reachable'))).toBe(false);
165
+ });
166
+ });
167
+
168
+ // -------------------------------------------------------------------------
169
+ // execute() — LCP threshold
170
+ // -------------------------------------------------------------------------
171
+
172
+ describe('LCP > 2500ms creates a finding', () => {
173
+ it('creates a high-severity finding when LCP exceeds 2500ms', async () => {
174
+ let callIndex = 0;
175
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
176
+ // For each module: loadTime=0, paintEntries=[], lcpMs=3000
177
+ const responses = [
178
+ 0, // dashboard loadTime
179
+ [], // dashboard paintEntries
180
+ 3000, // dashboard LCP — exceeds threshold
181
+ 0, // reports loadTime
182
+ [], // reports paintEntries
183
+ 3000, // reports LCP — exceeds threshold
184
+ 0, // heap
185
+ ];
186
+ return responses[callIndex++] ?? 0;
187
+ });
188
+
189
+ const agent = createAgent();
190
+ const result = await agent.run();
191
+
192
+ const lcpFindings = result.findings.filter((f) => f.id.includes('lcp'));
193
+ expect(lcpFindings.length).toBeGreaterThan(0);
194
+ expect(lcpFindings[0].severity).toBe('high');
195
+ expect(lcpFindings[0].description).toContain('3000');
196
+ });
197
+
198
+ it('does NOT create a finding when LCP is within threshold', async () => {
199
+ // All evaluate calls return 0 (lcpMs=0, well under 2500)
200
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(0);
201
+
202
+ const agent = createAgent();
203
+ const result = await agent.run();
204
+
205
+ const lcpFindings = result.findings.filter((f) => f.id.includes('lcp'));
206
+ expect(lcpFindings).toHaveLength(0);
207
+ });
208
+ });
209
+
210
+ // -------------------------------------------------------------------------
211
+ // FCP threshold
212
+ // -------------------------------------------------------------------------
213
+
214
+ describe('FCP threshold', () => {
215
+ it('creates a medium-severity finding when FCP exceeds 1800ms', async () => {
216
+ let callIndex = 0;
217
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
218
+ const responses: unknown[] = [
219
+ 0, // dashboard loadTime
220
+ [{ name: 'first-contentful-paint', startTime: 2500 }], // FCP > 1800
221
+ 0, // dashboard LCP
222
+ 0, // reports loadTime
223
+ [{ name: 'first-contentful-paint', startTime: 2500 }],
224
+ 0, // reports LCP
225
+ 0, // heap
226
+ ];
227
+ return responses[callIndex++] ?? 0;
228
+ });
229
+
230
+ const agent = createAgent();
231
+ const result = await agent.run();
232
+
233
+ const fcpFindings = result.findings.filter((f) => f.id.includes('fcp'));
234
+ expect(fcpFindings.length).toBeGreaterThan(0);
235
+ expect(fcpFindings[0].severity).toBe('medium');
236
+ });
237
+ });
238
+
239
+ // -------------------------------------------------------------------------
240
+ // Memory leak detection
241
+ // -------------------------------------------------------------------------
242
+
243
+ describe('memory leak detection', () => {
244
+ it('creates a high-severity finding when heap exceeds 100MB', async () => {
245
+ const OVER_100_MB = 110 * 1024 * 1024; // 110 MB
246
+ let callIndex = 0;
247
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
248
+ const responses = [
249
+ // dashboard
250
+ 0, [], 0,
251
+ // reports
252
+ 0, [], 0,
253
+ // heap check — over 100 MB
254
+ OVER_100_MB,
255
+ ];
256
+ return responses[callIndex++] ?? 0;
257
+ });
258
+
259
+ const agent = createAgent();
260
+ const result = await agent.run();
261
+
262
+ const heapFinding = result.findings.find((f) => f.id === '13-memory-heap');
263
+ expect(heapFinding).toBeDefined();
264
+ expect(heapFinding!.severity).toBe('high');
265
+ expect(heapFinding!.description).toContain('memory leak');
266
+ });
267
+
268
+ it('does NOT flag memory when heap is within threshold', async () => {
269
+ const UNDER_100_MB = 50 * 1024 * 1024; // 50 MB
270
+ let callIndex = 0;
271
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
272
+ const responses = [
273
+ 0, [], 0,
274
+ 0, [], 0,
275
+ UNDER_100_MB,
276
+ ];
277
+ return responses[callIndex++] ?? 0;
278
+ });
279
+
280
+ const agent = createAgent();
281
+ const result = await agent.run();
282
+
283
+ const heapFinding = result.findings.find((f) => f.id === '13-memory-heap');
284
+ expect(heapFinding).toBeUndefined();
285
+ });
286
+ });
287
+
288
+ // -------------------------------------------------------------------------
289
+ // Navigation failure
290
+ // -------------------------------------------------------------------------
291
+
292
+ describe('navigation failure', () => {
293
+ it('creates an infra-issue finding when module navigation throws', async () => {
294
+ navigateToSectionMock.mockRejectedValueOnce(new Error('Navigation timeout'));
295
+
296
+ const agent = createAgent();
297
+ const result = await agent.run();
298
+
299
+ const navFinding = result.findings.find((f) => f.id.includes('nav-dashboard'));
300
+ expect(navFinding).toBeDefined();
301
+ expect(navFinding!.type).toBe('infra-issue');
302
+ });
303
+ });
304
+
305
+ // -------------------------------------------------------------------------
306
+ // Passed with no violations
307
+ // -------------------------------------------------------------------------
308
+
309
+ it('returns passed status when all metrics are within thresholds', async () => {
310
+ // Each evaluate call must return the correct type:
311
+ // Per module: [loadTime: number, paintEntries: array, lcpMs: number]
312
+ // After all modules: [heap: number]
313
+ let callIdx = 0;
314
+ const responses: unknown[] = [
315
+ 0, // dashboard loadTime
316
+ [], // dashboard paintEntries (array — fcpEntry will be undefined → fcpMs = 0)
317
+ 0, // dashboard lcpMs
318
+ 0, // reports loadTime
319
+ [], // reports paintEntries
320
+ 0, // reports lcpMs
321
+ 0, // heap
322
+ ];
323
+
324
+ const freshPage: MockPage = {
325
+ close: vi.fn().mockResolvedValue(undefined),
326
+ evaluate: vi.fn().mockImplementation(async () => responses[callIdx++] ?? 0),
327
+ };
328
+ mockBrowser = {
329
+ newPage: vi.fn().mockResolvedValue(freshPage),
330
+ close: vi.fn().mockResolvedValue(undefined),
331
+ };
332
+
333
+ const agent = createAgent();
334
+ const result = await agent.run();
335
+
336
+ expect(result.status).toBe('passed');
337
+ });
338
+
339
+ // -------------------------------------------------------------------------
340
+ // No credentials
341
+ // -------------------------------------------------------------------------
342
+
343
+ it('creates a critical finding when no credentials are configured', async () => {
344
+ const noCreds: ValidatedConfig = {
345
+ ...stubConfig,
346
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
347
+ };
348
+ const agent = new PerformanceProfilerAgent(noCreds, 'beta', '/tmp/test-run');
349
+ const result = await agent.run();
350
+
351
+ const finding = result.findings.find(f => f.id === '13-no-credentials');
352
+ expect(finding).toBeDefined();
353
+ expect(finding!.severity).toBe('critical');
354
+ });
355
+
356
+ // -------------------------------------------------------------------------
357
+ // Load time threshold exceeded
358
+ // -------------------------------------------------------------------------
359
+
360
+ describe('page load time threshold', () => {
361
+ it('creates a medium finding when loadTime exceeds 2x apiResponseTime', async () => {
362
+ let callIndex = 0;
363
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
364
+ // apiResponseTime is 2000, threshold is 4000 (2x)
365
+ const responses: unknown[] = [
366
+ 5000, // dashboard loadTime — exceeds 4000
367
+ [], // dashboard paintEntries
368
+ 0, // dashboard LCP
369
+ 5000, // reports loadTime — exceeds 4000
370
+ [], // reports paintEntries
371
+ 0, // reports LCP
372
+ 0, // heap
373
+ ];
374
+ return responses[callIndex++] ?? 0;
375
+ });
376
+
377
+ const agent = createAgent();
378
+ const result = await agent.run();
379
+
380
+ const loadFindings = result.findings.filter(f => f.id.includes('load-time'));
381
+ expect(loadFindings.length).toBeGreaterThan(0);
382
+ expect(loadFindings[0].severity).toBe('medium');
383
+ });
384
+ });
385
+
386
+ // -------------------------------------------------------------------------
387
+ // Evidence logging
388
+ // -------------------------------------------------------------------------
389
+
390
+ it('adds log evidence for each module profiled', async () => {
391
+ const agent = createAgent();
392
+ const result = await agent.run();
393
+
394
+ const logEvidence = result.evidence.filter(e => e.type === 'log');
395
+ expect(logEvidence.length).toBeGreaterThanOrEqual(stubConfig.modules.length);
396
+ });
397
+
398
+ // -------------------------------------------------------------------------
399
+ // Evaluate catch paths (page.evaluate rejection)
400
+ // -------------------------------------------------------------------------
401
+
402
+ it('handles page.evaluate rejections gracefully', async () => {
403
+ currentMockPage.evaluate = vi.fn().mockRejectedValue(new Error('Page crashed'));
404
+
405
+ const agent = createAgent();
406
+ const result = await agent.run();
407
+
408
+ // Should still complete without throwing
409
+ expect(result.status).toBe('passed');
410
+ });
411
+ });