@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,443 @@
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 (used in preFlight via stat/readdir)
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', () => {
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
+ it('has agentId 12 and agentName "UX Inspector"', () => {
164
+ const agent = createAgent();
165
+ expect(agent.agentId).toBe(12);
166
+ expect(agent.agentName).toBe('UX Inspector');
167
+ });
168
+
169
+ // -------------------------------------------------------------------------
170
+ // preFlight()
171
+ // -------------------------------------------------------------------------
172
+
173
+ describe('preFlight()', () => {
174
+ it('throws when run directory does not exist', async () => {
175
+ statMock.mockRejectedValue(new Error('ENOENT'));
176
+
177
+ const agent = createAgent();
178
+ const result = await agent.run();
179
+
180
+ expect(result.status).toBe('failed');
181
+ expect(result.findings[0].description).toContain('Run directory does not exist');
182
+ });
183
+
184
+ it('does not throw on missing screenshots (advisory agent)', async () => {
185
+ statMock.mockResolvedValue({});
186
+ readdirMock.mockResolvedValue([]);
187
+
188
+ const agent = createAgent();
189
+ const result = await agent.run();
190
+
191
+ // Advisory agent — missing screenshots no longer causes preFlight failure.
192
+ // execute() still runs (and may produce findings from other checks), but
193
+ // the preFlight itself should not be the source of a "No screenshots" error.
194
+ const preflightScreenshotError = result.findings.find(
195
+ (f) => f.description.includes('No screenshots found'),
196
+ );
197
+ expect(preflightScreenshotError).toBeUndefined();
198
+ });
199
+
200
+ it('passes when run directory has PNG files', async () => {
201
+ const agent = createAgent();
202
+ const result = await agent.run();
203
+
204
+ expect(result.findings.some((f) => f.description.includes('No screenshots found'))).toBe(false);
205
+ });
206
+ });
207
+
208
+ // -------------------------------------------------------------------------
209
+ // execute() — general
210
+ // -------------------------------------------------------------------------
211
+
212
+ describe('execute()', () => {
213
+ it('calls login() once before iterating modules', async () => {
214
+ const agent = createAgent();
215
+ await agent.run();
216
+
217
+ expect(loginMock).toHaveBeenCalledTimes(1);
218
+ });
219
+
220
+ it('navigates to each configured module', async () => {
221
+ const agent = createAgent();
222
+ await agent.run();
223
+
224
+ expect(navigateToSectionMock).toHaveBeenCalledTimes(stubConfig.modules.length * 2);
225
+ });
226
+
227
+ it('returns passed status when no UX issues found', async () => {
228
+ // evaluate returns true (data rows present) — no empty-state finding
229
+ currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
230
+ // isVisible: error boundary IS found when API errors occur (so no finding),
231
+ // and no loading indicators stuck
232
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
233
+ // Error boundary selectors — signal that the app handles errors correctly
234
+ if (selector === '[data-testid="error-boundary"]' ||
235
+ selector === '[data-testid="error-fallback"]' ||
236
+ selector === '.error-boundary' ||
237
+ selector === '[role="alert"]') {
238
+ return Promise.resolve(true);
239
+ }
240
+ // Loading spinner selectors — not stuck
241
+ return Promise.resolve(false);
242
+ });
243
+
244
+ const agent = createAgent();
245
+ const result = await agent.run();
246
+
247
+ expect(result.status).toBe('passed');
248
+ });
249
+ });
250
+
251
+ // -------------------------------------------------------------------------
252
+ // ErrorBoundary check
253
+ // -------------------------------------------------------------------------
254
+
255
+ describe('API error → ErrorBoundary check', () => {
256
+ it('creates a finding when ErrorBoundary is NOT rendered on API error', async () => {
257
+ // page.isVisible always returns false — no error boundary selector matches
258
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
259
+
260
+ const agent = createAgent();
261
+ const result = await agent.run();
262
+
263
+ const ebFinding = result.findings.find((f) => f.id.includes('error-boundary'));
264
+ expect(ebFinding).toBeDefined();
265
+ expect(ebFinding!.severity).toBe('high');
266
+ expect(ebFinding!.type).toBe('code-bug-logic');
267
+ });
268
+
269
+ it('does NOT create a finding when ErrorBoundary IS rendered', async () => {
270
+ // Return true for role="alert" (one of the error boundary selectors)
271
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
272
+ if (selector === '[role="alert"]') return Promise.resolve(true);
273
+ return Promise.resolve(false);
274
+ });
275
+
276
+ const agent = createAgent();
277
+ const result = await agent.run();
278
+
279
+ const ebFindings = result.findings.filter((f) => f.id.includes('error-boundary'));
280
+ expect(ebFindings).toHaveLength(0);
281
+ });
282
+ });
283
+
284
+ // -------------------------------------------------------------------------
285
+ // Empty state check
286
+ // -------------------------------------------------------------------------
287
+
288
+ describe('empty state detection', () => {
289
+ it('creates a finding when data is empty and no empty-state element shown', async () => {
290
+ // evaluate returns false (no data rows), isVisible returns false (no empty state)
291
+ currentMockPage.evaluate = vi.fn().mockResolvedValueOnce(false); // no data rows
292
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
293
+
294
+ const agent = createAgent();
295
+ const result = await agent.run();
296
+
297
+ const emptyFinding = result.findings.find((f) => f.id.includes('empty-state'));
298
+ expect(emptyFinding).toBeDefined();
299
+ expect(emptyFinding!.severity).toBe('low');
300
+ });
301
+
302
+ it('does NOT create a finding when empty state element is visible', async () => {
303
+ // evaluate returns false (no data), but empty-state selector is visible
304
+ currentMockPage.evaluate = vi.fn().mockResolvedValueOnce(false);
305
+ currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
306
+ if (selector === '[data-testid="empty-state"]') return Promise.resolve(true);
307
+ return Promise.resolve(false);
308
+ });
309
+
310
+ const agent = createAgent();
311
+ const result = await agent.run();
312
+
313
+ const emptyFindings = result.findings.filter((f) => f.id.includes('empty-state'));
314
+ expect(emptyFindings).toHaveLength(0);
315
+ });
316
+ });
317
+
318
+ // -------------------------------------------------------------------------
319
+ // Navigation failure
320
+ // -------------------------------------------------------------------------
321
+
322
+ describe('navigation failure', () => {
323
+ it('creates an infra-issue finding when module navigation throws', async () => {
324
+ navigateToSectionMock.mockRejectedValueOnce(new Error('Timeout navigating to dashboard'));
325
+
326
+ const agent = createAgent();
327
+ const result = await agent.run();
328
+
329
+ const navFinding = result.findings.find((f) => f.id.includes('nav-dashboard'));
330
+ expect(navFinding).toBeDefined();
331
+ expect(navFinding!.type).toBe('infra-issue');
332
+ });
333
+ });
334
+
335
+ // -------------------------------------------------------------------------
336
+ // Loading state stuck
337
+ // -------------------------------------------------------------------------
338
+
339
+ describe('loading state detection', () => {
340
+ it('creates a medium finding when a loading spinner is still visible', async () => {
341
+ currentMockPage.isVisible = vi.fn().mockImplementation(async (selector: string) => {
342
+ if (selector === '[data-testid="loading-spinner"]') return true;
343
+ return false;
344
+ });
345
+
346
+ const agent = createAgent();
347
+ const result = await agent.run();
348
+
349
+ const loadingFinding = result.findings.find(f => f.id.includes('loading-stuck'));
350
+ expect(loadingFinding).toBeDefined();
351
+ expect(loadingFinding!.severity).toBe('medium');
352
+ });
353
+ });
354
+
355
+ // -------------------------------------------------------------------------
356
+ // Form validation check
357
+ // -------------------------------------------------------------------------
358
+
359
+ describe('form validation check', () => {
360
+ it('creates a finding when form submit does not show inline validation', async () => {
361
+ currentMockPage.isVisible = vi.fn().mockImplementation(async (selector: string) => {
362
+ if (selector === 'form') return true;
363
+ return false;
364
+ });
365
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
366
+ // hasRequiredField returns true, hasDataRows returns true
367
+ return true;
368
+ });
369
+ currentMockPage.locator = vi.fn().mockReturnValue({
370
+ first: () => ({
371
+ isVisible: vi.fn().mockResolvedValue(true),
372
+ click: vi.fn().mockResolvedValue(undefined),
373
+ }),
374
+ });
375
+
376
+ const agent = createAgent();
377
+ const result = await agent.run();
378
+
379
+ const formFinding = result.findings.find(f => f.id.includes('form-validation'));
380
+ expect(formFinding).toBeDefined();
381
+ expect(formFinding!.severity).toBe('medium');
382
+ });
383
+ });
384
+
385
+ // -------------------------------------------------------------------------
386
+ // Truncated text check
387
+ // -------------------------------------------------------------------------
388
+
389
+ describe('truncated text detection', () => {
390
+ it('creates a low finding when truncated elements are detected', async () => {
391
+ currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
392
+ return 3; // 3 truncated elements
393
+ });
394
+ currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
395
+
396
+ const agent = createAgent();
397
+ const result = await agent.run();
398
+
399
+ const truncFinding = result.findings.find(f => f.id.includes('truncated-text'));
400
+ expect(truncFinding).toBeDefined();
401
+ expect(truncFinding!.severity).toBe('low');
402
+ expect(truncFinding!.description).toContain('3');
403
+ });
404
+ });
405
+
406
+ // -------------------------------------------------------------------------
407
+ // No environment configured
408
+ // -------------------------------------------------------------------------
409
+
410
+ describe('no environment', () => {
411
+ it('creates a critical finding when no environment is configured', async () => {
412
+ const noEnvConfig: ValidatedConfig = {
413
+ ...stubConfig,
414
+ environments: {},
415
+ };
416
+ const agent = new UxInspectorAgent(noEnvConfig, 'beta', '/tmp/test-run');
417
+ const result = await agent.run();
418
+
419
+ const finding = result.findings.find(f => f.id === '12-no-env');
420
+ expect(finding).toBeDefined();
421
+ expect(finding!.severity).toBe('critical');
422
+ });
423
+ });
424
+
425
+ // -------------------------------------------------------------------------
426
+ // No credentials
427
+ // -------------------------------------------------------------------------
428
+
429
+ describe('no credentials', () => {
430
+ it('creates a critical finding when no credentials are configured', async () => {
431
+ const noCredConfig: ValidatedConfig = {
432
+ ...stubConfig,
433
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
434
+ };
435
+ const agent = new UxInspectorAgent(noCredConfig, 'beta', '/tmp/test-run');
436
+ const result = await agent.run();
437
+
438
+ const finding = result.findings.find(f => f.id === '12-no-credentials');
439
+ expect(finding).toBeDefined();
440
+ expect(finding!.severity).toBe('critical');
441
+ });
442
+ });
443
+ });