@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,465 @@
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: 'clients', route: '/clients', 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 node:fs/promises
45
+ // ---------------------------------------------------------------------------
46
+
47
+ const statMock = vi.fn();
48
+ const readdirMock = vi.fn();
49
+
50
+ vi.mock('node:fs/promises', async (importOriginal) => {
51
+ const actual = await importOriginal<typeof import('node:fs/promises')>();
52
+ return {
53
+ ...actual,
54
+ stat: (...args: unknown[]) => statMock(...args),
55
+ readdir: (...args: unknown[]) => readdirMock(...args),
56
+ };
57
+ });
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Mock navigation helpers
61
+ // ---------------------------------------------------------------------------
62
+
63
+ const loginMock = vi.fn().mockResolvedValue(undefined);
64
+ const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
65
+
66
+ vi.mock('../../helpers/navigation', () => ({
67
+ login: (...args: unknown[]) => loginMock(...args),
68
+ navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
69
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
70
+ }));
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Mock screenshot helper
74
+ // ---------------------------------------------------------------------------
75
+
76
+ const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
77
+
78
+ vi.mock('../../helpers/screenshot', () => ({
79
+ captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
80
+ }));
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Mock Playwright page
84
+ // ---------------------------------------------------------------------------
85
+
86
+ type RouteHandler = (route: { fulfill: (opts: Record<string, unknown>) => Promise<void> }) => void | Promise<void>;
87
+
88
+ interface MockPage {
89
+ close: ReturnType<typeof vi.fn>;
90
+ goto: ReturnType<typeof vi.fn>;
91
+ reload: ReturnType<typeof vi.fn>;
92
+ isVisible: ReturnType<typeof vi.fn>;
93
+ evaluate: ReturnType<typeof vi.fn>;
94
+ route: ReturnType<typeof vi.fn>;
95
+ unroute: ReturnType<typeof vi.fn>;
96
+ waitForTimeout: ReturnType<typeof vi.fn>;
97
+ locator: ReturnType<typeof vi.fn>;
98
+ _routeHandlers: Map<string, RouteHandler>;
99
+ }
100
+
101
+ function makeMockPage(overrides: Partial<MockPage> = {}): MockPage {
102
+ const routeHandlers = new Map<string, RouteHandler>();
103
+
104
+ const page: MockPage = {
105
+ close: vi.fn().mockResolvedValue(undefined),
106
+ goto: vi.fn().mockResolvedValue(undefined),
107
+ reload: vi.fn().mockResolvedValue(undefined),
108
+ isVisible: vi.fn().mockResolvedValue(false),
109
+ evaluate: vi.fn().mockResolvedValue(false),
110
+ route: vi.fn().mockImplementation(async (pattern: string, handler: RouteHandler) => {
111
+ routeHandlers.set(pattern, handler);
112
+ }),
113
+ unroute: vi.fn().mockResolvedValue(undefined),
114
+ waitForTimeout: vi.fn().mockResolvedValue(undefined),
115
+ locator: vi.fn().mockReturnValue({
116
+ first: () => ({ isVisible: vi.fn().mockResolvedValue(false) }),
117
+ }),
118
+ _routeHandlers: routeHandlers,
119
+ ...overrides,
120
+ };
121
+
122
+ return page;
123
+ }
124
+
125
+ let currentMockPage: MockPage;
126
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
127
+
128
+ vi.mock('playwright', () => ({
129
+ chromium: {
130
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
131
+ },
132
+ }));
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Dynamic import AFTER mocks
136
+ // ---------------------------------------------------------------------------
137
+
138
+ const { UxInspectorAgent } = await import('../12-ux-inspector');
139
+
140
+ function createAgent(phase: Phase = 'beta'): InstanceType<typeof UxInspectorAgent> {
141
+ return new UxInspectorAgent(stubConfig, phase, '/tmp/test-run');
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Tests
146
+ // ---------------------------------------------------------------------------
147
+
148
+ describe('UxInspectorAgent — extended coverage', () => {
149
+ beforeEach(() => {
150
+ statMock.mockReset().mockResolvedValue({});
151
+ readdirMock.mockReset().mockResolvedValue(['smoke-dashboard.png', 'smoke-clients.png']);
152
+ loginMock.mockReset().mockResolvedValue(undefined);
153
+ navigateToSectionMock.mockReset().mockResolvedValue(undefined);
154
+ captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
155
+
156
+ currentMockPage = makeMockPage();
157
+ mockBrowser = {
158
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
159
+ close: vi.fn().mockResolvedValue(undefined),
160
+ };
161
+ });
162
+
163
+ // -------------------------------------------------------------------------
164
+ // No environment configured
165
+ // -------------------------------------------------------------------------
166
+
167
+ describe('no environment', () => {
168
+ it('returns critical finding when no environment is configured', async () => {
169
+ const noEnvConfig: ValidatedConfig = {
170
+ ...stubConfig,
171
+ environments: {},
172
+ };
173
+ const agent = new UxInspectorAgent(noEnvConfig, 'beta', '/tmp/test-run');
174
+ const result = await agent.run();
175
+
176
+ const finding = result.findings.find((f) => f.id === '12-no-env');
177
+ expect(finding).toBeDefined();
178
+ expect(finding!.severity).toBe('critical');
179
+ });
180
+ });
181
+
182
+ // -------------------------------------------------------------------------
183
+ // No credentials
184
+ // -------------------------------------------------------------------------
185
+
186
+ describe('no credentials', () => {
187
+ it('returns critical finding when no credentials configured', async () => {
188
+ const noCredConfig: ValidatedConfig = {
189
+ ...stubConfig,
190
+ auth: { loginUrl: '/login', credentials: {} },
191
+ };
192
+ const agent = new UxInspectorAgent(noCredConfig, 'beta', '/tmp/test-run');
193
+ const result = await agent.run();
194
+
195
+ const finding = result.findings.find((f) => f.id === '12-no-credentials');
196
+ expect(finding).toBeDefined();
197
+ expect(finding!.severity).toBe('critical');
198
+ });
199
+ });
200
+
201
+ // -------------------------------------------------------------------------
202
+ // Loading states stuck
203
+ // -------------------------------------------------------------------------
204
+
205
+ describe('loading states', () => {
206
+ it('creates finding when a loading spinner is stuck', async () => {
207
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
208
+ if (selector === '[data-testid="loading-spinner"]') return Promise.resolve(true);
209
+ return Promise.resolve(false);
210
+ });
211
+
212
+ const agent = createAgent();
213
+ const result = await agent.run();
214
+
215
+ const loadingFinding = result.findings.find((f) => f.id.includes('loading-stuck'));
216
+ expect(loadingFinding).toBeDefined();
217
+ expect(loadingFinding!.type).toBe('code-bug-cosmetic');
218
+ expect(loadingFinding!.severity).toBe('medium');
219
+ });
220
+
221
+ it('creates finding when skeleton loader is stuck', async () => {
222
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
223
+ if (selector === '.skeleton') return Promise.resolve(true);
224
+ return Promise.resolve(false);
225
+ });
226
+
227
+ const agent = createAgent();
228
+ const result = await agent.run();
229
+
230
+ const loadingFinding = result.findings.find((f) => f.id.includes('loading-stuck'));
231
+ expect(loadingFinding).toBeDefined();
232
+ });
233
+
234
+ it('does not create finding when no loading indicators visible', async () => {
235
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
236
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(true); // has data rows
237
+
238
+ const agent = createAgent();
239
+ const result = await agent.run();
240
+
241
+ const loadingFindings = result.findings.filter((f) => f.id.includes('loading-stuck'));
242
+ // May still get error-boundary findings, but no loading-stuck
243
+ expect(loadingFindings).toHaveLength(0);
244
+ });
245
+ });
246
+
247
+ // -------------------------------------------------------------------------
248
+ // Form validation checks
249
+ // -------------------------------------------------------------------------
250
+
251
+ describe('form validation', () => {
252
+ it('creates finding when form has required fields but no validation messages on submit', async () => {
253
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
254
+ if (selector === 'form') return Promise.resolve(true);
255
+ return Promise.resolve(false);
256
+ });
257
+ currentMockPage.evaluate = vi.fn().mockImplementation((fn: unknown) => {
258
+ // First evaluate call: check for data rows (in checkEmptyStates)
259
+ // Second evaluate call: check for required fields
260
+ if (typeof fn === 'function') {
261
+ return Promise.resolve(true); // has required fields or data rows
262
+ }
263
+ return Promise.resolve(true);
264
+ });
265
+ const clickMock = vi.fn().mockResolvedValue(undefined);
266
+ currentMockPage.locator = vi.fn().mockReturnValue({
267
+ first: () => ({
268
+ isVisible: vi.fn().mockResolvedValue(true),
269
+ click: clickMock,
270
+ }),
271
+ });
272
+
273
+ const agent = createAgent();
274
+ const result = await agent.run();
275
+
276
+ const validationFinding = result.findings.find((f) => f.id.includes('form-validation'));
277
+ expect(validationFinding).toBeDefined();
278
+ expect(validationFinding!.severity).toBe('medium');
279
+ });
280
+
281
+ it('does NOT create finding when form validation messages are shown', async () => {
282
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
283
+ if (selector === 'form') return Promise.resolve(true);
284
+ if (selector === '[data-testid="field-error"]') return Promise.resolve(true);
285
+ // Error boundary selectors
286
+ if (selector === '[role="alert"]') return Promise.resolve(true);
287
+ return Promise.resolve(false);
288
+ });
289
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
290
+ currentMockPage.locator = vi.fn().mockReturnValue({
291
+ first: () => ({
292
+ isVisible: vi.fn().mockResolvedValue(true),
293
+ click: vi.fn().mockResolvedValue(undefined),
294
+ }),
295
+ });
296
+
297
+ const agent = createAgent();
298
+ const result = await agent.run();
299
+
300
+ const validationFindings = result.findings.filter((f) => f.id.includes('form-validation'));
301
+ expect(validationFindings).toHaveLength(0);
302
+ });
303
+
304
+ it('skips form validation when no form is visible', async () => {
305
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
306
+ if (selector === 'form') return Promise.resolve(false);
307
+ return Promise.resolve(false);
308
+ });
309
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
310
+
311
+ const agent = createAgent();
312
+ const result = await agent.run();
313
+
314
+ const validationFindings = result.findings.filter((f) => f.id.includes('form-validation'));
315
+ expect(validationFindings).toHaveLength(0);
316
+ });
317
+ });
318
+
319
+ // -------------------------------------------------------------------------
320
+ // Truncated text detection
321
+ // -------------------------------------------------------------------------
322
+
323
+ describe('truncated text', () => {
324
+ it('creates finding when elements with truncated text are detected', async () => {
325
+ // evaluate is called multiple times:
326
+ // - checkEmptyStates (once per module) — should return true (data present)
327
+ // - checkTruncatedText (once per module) — should return count > 0
328
+ // - possibly checkFormValidation
329
+ // We use a counter to know which call is which: for 2 modules, calls 1-2 are empty-state,
330
+ // calls 3-4 (if reached) are truncated text.
331
+ let evalCallCount = 0;
332
+ currentMockPage.evaluate = vi.fn().mockImplementation(() => {
333
+ evalCallCount++;
334
+ // Calls for checkEmptyStates (2 modules): return true (data present)
335
+ // Calls for checkFormValidation: also calls evaluate
336
+ // Calls for checkTruncatedText: return count
337
+ // Since evaluate is used by checkEmptyStates, checkFormValidation AND checkTruncatedText
338
+ // we just return 3 for all — truthy means data present, and 3 means truncated elements
339
+ return Promise.resolve(3);
340
+ });
341
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
342
+ // Make error boundary found to avoid those findings
343
+ if (selector === '[role="alert"]') return Promise.resolve(true);
344
+ return Promise.resolve(false);
345
+ });
346
+
347
+ const agent = createAgent();
348
+ const result = await agent.run();
349
+
350
+ const truncatedFinding = result.findings.find((f) => f.id.includes('truncated-text'));
351
+ expect(truncatedFinding).toBeDefined();
352
+ expect(truncatedFinding!.severity).toBe('low');
353
+ expect(truncatedFinding!.description).toContain('element(s)');
354
+ });
355
+
356
+ it('does NOT create finding when no truncated text is detected', async () => {
357
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(0);
358
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
359
+ if (selector === '[role="alert"]') return Promise.resolve(true);
360
+ return Promise.resolve(false);
361
+ });
362
+
363
+ const agent = createAgent();
364
+ const result = await agent.run();
365
+
366
+ const truncatedFindings = result.findings.filter((f) => f.id.includes('truncated-text'));
367
+ expect(truncatedFindings).toHaveLength(0);
368
+ });
369
+ });
370
+
371
+ // -------------------------------------------------------------------------
372
+ // Browser cleanup on error
373
+ // -------------------------------------------------------------------------
374
+
375
+ describe('browser cleanup', () => {
376
+ it('closes browser and page even when login throws', async () => {
377
+ loginMock.mockRejectedValue(new Error('Login failed'));
378
+
379
+ const agent = createAgent();
380
+ const result = await agent.run();
381
+
382
+ expect(result.status).toBe('failed');
383
+ expect(currentMockPage.close).toHaveBeenCalled();
384
+ expect(mockBrowser.close).toHaveBeenCalled();
385
+ });
386
+ });
387
+
388
+ // -------------------------------------------------------------------------
389
+ // Screenshot evidence is captured
390
+ // -------------------------------------------------------------------------
391
+
392
+ describe('evidence collection', () => {
393
+ it('captures screenshots for loading-stuck findings', async () => {
394
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
395
+ if (selector === '[aria-label="Loading"]') return Promise.resolve(true);
396
+ return Promise.resolve(false);
397
+ });
398
+
399
+ const agent = createAgent();
400
+ const result = await agent.run();
401
+
402
+ const screenshotEvidence = result.evidence.filter((e) => e.type === 'screenshot');
403
+ expect(screenshotEvidence.length).toBeGreaterThan(0);
404
+ });
405
+
406
+ it('captures screenshots for error-boundary findings', async () => {
407
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
408
+
409
+ const agent = createAgent();
410
+ const result = await agent.run();
411
+
412
+ const screenshotEvidence = result.evidence.filter((e) => e.type === 'screenshot');
413
+ expect(screenshotEvidence.length).toBeGreaterThan(0);
414
+ });
415
+ });
416
+
417
+ // -------------------------------------------------------------------------
418
+ // Environment fallback
419
+ // -------------------------------------------------------------------------
420
+
421
+ describe('environment fallback', () => {
422
+ it('uses alpha environment when beta is not configured', async () => {
423
+ const alphaOnlyConfig: ValidatedConfig = {
424
+ ...stubConfig,
425
+ environments: {
426
+ alpha: { baseUrl: 'http://alpha.localhost:3000', seed: false },
427
+ },
428
+ };
429
+
430
+ const agent = new UxInspectorAgent(alphaOnlyConfig, 'beta', '/tmp/test-run');
431
+ const result = await agent.run();
432
+
433
+ // Should not produce a no-env finding
434
+ const noEnvFinding = result.findings.find((f) => f.id === '12-no-env');
435
+ expect(noEnvFinding).toBeUndefined();
436
+ });
437
+ });
438
+
439
+ // -------------------------------------------------------------------------
440
+ // isVisible throws (error resilience)
441
+ // -------------------------------------------------------------------------
442
+
443
+ describe('error resilience', () => {
444
+ it('handles isVisible throwing gracefully in loading state check', async () => {
445
+ currentMockPage.isVisible = vi.fn().mockRejectedValue(new Error('Detached'));
446
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
447
+
448
+ const agent = createAgent();
449
+ const result = await agent.run();
450
+
451
+ // Should not crash; the checks are best-effort
452
+ expect(result.status).toBeDefined();
453
+ });
454
+
455
+ it('handles evaluate throwing gracefully in empty state check', async () => {
456
+ currentMockPage.evaluate = vi.fn().mockRejectedValue(new Error('Execution context was destroyed'));
457
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
458
+
459
+ const agent = createAgent();
460
+ const result = await agent.run();
461
+
462
+ expect(result.status).toBeDefined();
463
+ });
464
+ });
465
+ });