@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,530 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Stub config
6
+ // ---------------------------------------------------------------------------
7
+
8
+ const stubConfig: ValidatedConfig = {
9
+ schemaVersion: 1,
10
+ name: 'test',
11
+ auth: {
12
+ loginUrl: 'http://localhost/login',
13
+ credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
14
+ },
15
+ modules: [
16
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
17
+ { id: 'invoices', route: '/invoices', sidebarIcon: true },
18
+ ],
19
+ environments: {
20
+ alpha: { baseUrl: 'http://localhost:3000', seed: false },
21
+ },
22
+ portals: [],
23
+ breakpoints: [],
24
+ integrations: [],
25
+ workers: [],
26
+ workflows: [],
27
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
28
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
29
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
30
+ agentClassification: { blocking: [], advisory: [] },
31
+ thresholds: {
32
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
33
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
34
+ apiResponseTime: 2000,
35
+ bundleSizeLimit: 500000,
36
+ maxConsoleErrors: 0,
37
+ },
38
+ disabledAgents: [],
39
+ disabledAgentSet: new Set<number>(),
40
+ };
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Mock child_process (preFlight playwright --version, lhci, pa11y-ci)
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const execFileMock = vi.fn();
47
+ vi.mock('child_process', () => ({
48
+ execFile: (...args: unknown[]) => execFileMock(...args),
49
+ }));
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Mock navigation helpers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const loginMock = vi.fn().mockResolvedValue(undefined);
56
+ const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
57
+ const waitForSectionLoadMock = vi.fn().mockResolvedValue(undefined);
58
+
59
+ vi.mock('../../helpers/navigation', () => ({
60
+ login: (...args: unknown[]) => loginMock(...args),
61
+ navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
62
+ waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
63
+ }));
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Mock screenshot helper
67
+ // ---------------------------------------------------------------------------
68
+
69
+ const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
70
+ vi.mock('../../helpers/screenshot', () => ({
71
+ captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
72
+ }));
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Mock AxeBuilder
76
+ // ---------------------------------------------------------------------------
77
+
78
+ const analyzeMock = vi.fn();
79
+
80
+ vi.mock('@axe-core/playwright', () => ({
81
+ AxeBuilder: class MockAxeBuilder {
82
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
+ constructor(..._args: unknown[]) {}
84
+ analyze = analyzeMock;
85
+ },
86
+ }));
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Mock Playwright
90
+ // ---------------------------------------------------------------------------
91
+
92
+ interface MockPage {
93
+ close: ReturnType<typeof vi.fn>;
94
+ }
95
+
96
+ function makeMockPage(): MockPage {
97
+ return { close: vi.fn().mockResolvedValue(undefined) };
98
+ }
99
+
100
+ let currentMockPage: MockPage;
101
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
102
+
103
+ vi.mock('playwright', async () => ({
104
+ chromium: {
105
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
106
+ },
107
+ }));
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Dynamic import AFTER mocks
111
+ // ---------------------------------------------------------------------------
112
+
113
+ const { A11yGuardianAgent, mapAxeImpact } = await import('../08-a11y-guardian');
114
+
115
+ function createAgent(): InstanceType<typeof A11yGuardianAgent> {
116
+ return new A11yGuardianAgent(stubConfig, 'beta', '/tmp/test-run');
117
+ }
118
+
119
+ function makeExecSuccess(): void {
120
+ execFileMock.mockImplementationOnce(
121
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
122
+ cb(null, '1.40.0\n', '');
123
+ },
124
+ );
125
+ }
126
+
127
+ function makeExecFailure(): void {
128
+ execFileMock.mockImplementationOnce(
129
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
130
+ cb(new Error('command not found'), '', '');
131
+ },
132
+ );
133
+ }
134
+
135
+ function makeCleanAxeResult(): object {
136
+ return { violations: [] };
137
+ }
138
+
139
+ function makeViolation(impact: string, id = 'button-name'): object {
140
+ return {
141
+ id,
142
+ description: `Ensure ${id} elements have discernible text`,
143
+ impact,
144
+ nodes: [{ html: '<button></button>', target: ['button'] }],
145
+ help: `https://dequeuniversity.com/rules/${id}`,
146
+ };
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Tests
151
+ // ---------------------------------------------------------------------------
152
+
153
+ describe('A11yGuardianAgent', () => {
154
+ beforeEach(() => {
155
+ execFileMock.mockReset();
156
+ loginMock.mockReset().mockResolvedValue(undefined);
157
+ navigateToSectionMock.mockReset().mockResolvedValue(undefined);
158
+ waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
159
+ captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
160
+ analyzeMock.mockReset().mockResolvedValue(makeCleanAxeResult());
161
+
162
+ currentMockPage = makeMockPage();
163
+ mockBrowser = {
164
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
165
+ close: vi.fn().mockResolvedValue(undefined),
166
+ };
167
+
168
+ // Default: preFlight succeeds; lhci and pa11y-ci fail silently (not installed)
169
+ makeExecSuccess(); // playwright --version (preFlight)
170
+ execFileMock.mockImplementation( // lhci and pa11y-ci: fail silently
171
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
172
+ cb(new Error('not found'), '', '');
173
+ },
174
+ );
175
+ });
176
+
177
+ // -------------------------------------------------------------------------
178
+ // Identity
179
+ // -------------------------------------------------------------------------
180
+
181
+ it('has agentId 8 and agentName "A11y Guardian"', () => {
182
+ const agent = createAgent();
183
+ expect(agent.agentId).toBe(8);
184
+ expect(agent.agentName).toBe('A11y Guardian');
185
+ });
186
+
187
+ // -------------------------------------------------------------------------
188
+ // mapAxeImpact utility
189
+ // -------------------------------------------------------------------------
190
+
191
+ describe('mapAxeImpact()', () => {
192
+ it('maps "critical" → "critical"', () => {
193
+ expect(mapAxeImpact('critical')).toBe('critical');
194
+ });
195
+
196
+ it('maps "serious" → "high"', () => {
197
+ expect(mapAxeImpact('serious')).toBe('high');
198
+ });
199
+
200
+ it('maps "moderate" → "medium"', () => {
201
+ expect(mapAxeImpact('moderate')).toBe('medium');
202
+ });
203
+
204
+ it('maps "minor" → "low"', () => {
205
+ expect(mapAxeImpact('minor')).toBe('low');
206
+ });
207
+
208
+ it('maps null → "low"', () => {
209
+ expect(mapAxeImpact(null)).toBe('low');
210
+ });
211
+
212
+ it('maps unknown strings → "low"', () => {
213
+ expect(mapAxeImpact('unknown-impact')).toBe('low');
214
+ });
215
+ });
216
+
217
+ // -------------------------------------------------------------------------
218
+ // preFlight
219
+ // -------------------------------------------------------------------------
220
+
221
+ describe('preFlight()', () => {
222
+ it('fails when playwright is not available', async () => {
223
+ execFileMock.mockReset();
224
+ makeExecFailure();
225
+
226
+ const agent = createAgent();
227
+ const result = await agent.run();
228
+
229
+ expect(result.status).toBe('failed');
230
+ });
231
+ });
232
+
233
+ // -------------------------------------------------------------------------
234
+ // execute() — module iteration
235
+ // -------------------------------------------------------------------------
236
+
237
+ describe('execute()', () => {
238
+ it('logs in once before scanning modules', async () => {
239
+ const agent = createAgent();
240
+ await agent.run();
241
+
242
+ expect(loginMock).toHaveBeenCalledTimes(1);
243
+ expect(loginMock).toHaveBeenCalledWith(
244
+ currentMockPage,
245
+ stubConfig.auth.credentials!['admin'],
246
+ stubConfig.auth.loginUrl,
247
+ );
248
+ });
249
+
250
+ it('navigates to each configured module', async () => {
251
+ const agent = createAgent();
252
+ await agent.run();
253
+
254
+ expect(navigateToSectionMock).toHaveBeenCalledTimes(stubConfig.modules.length);
255
+ });
256
+
257
+ it('runs axe-core scan for each module', async () => {
258
+ const agent = createAgent();
259
+ await agent.run();
260
+
261
+ expect(analyzeMock).toHaveBeenCalledTimes(stubConfig.modules.length);
262
+ });
263
+
264
+ it('takes a screenshot for each module', async () => {
265
+ const agent = createAgent();
266
+ const result = await agent.run();
267
+
268
+ const screenshotEvidence = result.evidence.filter(e => e.type === 'screenshot');
269
+ expect(screenshotEvidence).toHaveLength(stubConfig.modules.length);
270
+ });
271
+
272
+ it('returns passed status with no findings when all modules are clean', async () => {
273
+ const agent = createAgent();
274
+ const result = await agent.run();
275
+
276
+ expect(result.status).toBe('passed');
277
+ expect(result.findings).toHaveLength(0);
278
+ });
279
+
280
+ it('creates a finding for each axe violation per module', async () => {
281
+ analyzeMock.mockResolvedValue({
282
+ violations: [
283
+ makeViolation('serious', 'button-name'),
284
+ makeViolation('moderate', 'color-contrast'),
285
+ ],
286
+ });
287
+
288
+ const agent = createAgent();
289
+ const result = await agent.run();
290
+
291
+ // 2 violations × 2 modules = 4 findings
292
+ expect(result.findings).toHaveLength(4);
293
+ });
294
+ });
295
+
296
+ // -------------------------------------------------------------------------
297
+ // Severity mapping
298
+ // -------------------------------------------------------------------------
299
+
300
+ describe('severity mapping', () => {
301
+ it('maps critical axe impact to critical finding severity', async () => {
302
+ analyzeMock.mockResolvedValueOnce({ violations: [makeViolation('critical')] });
303
+ analyzeMock.mockResolvedValueOnce({ violations: [] });
304
+
305
+ const agent = createAgent();
306
+ const result = await agent.run();
307
+
308
+ const criticalFinding = result.findings.find(f => f.severity === 'critical');
309
+ expect(criticalFinding).toBeDefined();
310
+ });
311
+
312
+ it('maps serious axe impact to high finding severity', async () => {
313
+ analyzeMock.mockResolvedValueOnce({ violations: [makeViolation('serious')] });
314
+ analyzeMock.mockResolvedValueOnce({ violations: [] });
315
+
316
+ const agent = createAgent();
317
+ const result = await agent.run();
318
+
319
+ const highFinding = result.findings.find(f => f.severity === 'high');
320
+ expect(highFinding).toBeDefined();
321
+ expect(highFinding!.type).toBe('code-bug-logic');
322
+ });
323
+
324
+ it('maps moderate axe impact to medium finding severity', async () => {
325
+ analyzeMock.mockResolvedValueOnce({ violations: [makeViolation('moderate')] });
326
+ analyzeMock.mockResolvedValueOnce({ violations: [] });
327
+
328
+ const agent = createAgent();
329
+ const result = await agent.run();
330
+
331
+ const medFinding = result.findings.find(f => f.severity === 'medium');
332
+ expect(medFinding).toBeDefined();
333
+ });
334
+
335
+ it('maps minor axe impact to low finding severity', async () => {
336
+ analyzeMock.mockResolvedValueOnce({ violations: [makeViolation('minor')] });
337
+ analyzeMock.mockResolvedValueOnce({ violations: [] });
338
+
339
+ const agent = createAgent();
340
+ const result = await agent.run();
341
+
342
+ const lowFinding = result.findings.find(f => f.severity === 'low');
343
+ expect(lowFinding).toBeDefined();
344
+ });
345
+ });
346
+
347
+ // -------------------------------------------------------------------------
348
+ // Navigation failure
349
+ // -------------------------------------------------------------------------
350
+
351
+ describe('navigation failure handling', () => {
352
+ it('creates an infra-issue finding when navigation fails and continues to next module', async () => {
353
+ navigateToSectionMock.mockImplementation(
354
+ async (_page: unknown, moduleId: string) => {
355
+ if (moduleId === 'dashboard') {
356
+ throw new Error('Timeout navigating to dashboard');
357
+ }
358
+ },
359
+ );
360
+
361
+ const agent = createAgent();
362
+ const result = await agent.run();
363
+
364
+ const navFinding = result.findings.find(f =>
365
+ f.id.includes('nav-dashboard'),
366
+ );
367
+ expect(navFinding).toBeDefined();
368
+ expect(navFinding!.type).toBe('infra-issue');
369
+
370
+ // Should still scan the 'invoices' module
371
+ expect(analyzeMock).toHaveBeenCalledTimes(1);
372
+ });
373
+ });
374
+
375
+ // -------------------------------------------------------------------------
376
+ // axe failure handling
377
+ // -------------------------------------------------------------------------
378
+
379
+ describe('axe scan failure handling', () => {
380
+ it('creates a medium infra-issue finding when axe.analyze() throws', async () => {
381
+ analyzeMock.mockRejectedValueOnce(new Error('axe failed to inject'));
382
+
383
+ const agent = createAgent();
384
+ const result = await agent.run();
385
+
386
+ const axeErrorFinding = result.findings.find(f =>
387
+ f.id.includes('axe-error'),
388
+ );
389
+ expect(axeErrorFinding).toBeDefined();
390
+ expect(axeErrorFinding!.severity).toBe('medium');
391
+ });
392
+ });
393
+
394
+ // -------------------------------------------------------------------------
395
+ // Login failure handling
396
+ // -------------------------------------------------------------------------
397
+
398
+ describe('login failure handling', () => {
399
+ it('continues scanning anonymously when login fails', async () => {
400
+ loginMock.mockRejectedValue(new Error('Invalid credentials'));
401
+ // A11y agent needs page.goto for the anonymous fallback navigation
402
+ (currentMockPage as unknown as Record<string, unknown>).goto = vi.fn().mockResolvedValue(undefined);
403
+
404
+ const agent = createAgent();
405
+ const result = await agent.run();
406
+
407
+ const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
408
+ expect(loginFinding).toBeDefined();
409
+ expect(loginFinding!.severity).toBe('medium');
410
+
411
+ // Should still run axe scans
412
+ expect(analyzeMock).toHaveBeenCalled();
413
+ });
414
+ });
415
+
416
+ // -------------------------------------------------------------------------
417
+ // Lighthouse CI (optional)
418
+ // -------------------------------------------------------------------------
419
+
420
+ describe('Lighthouse CI integration', () => {
421
+ it('adds report evidence when lhci autorun succeeds', async () => {
422
+ // Reset default mock first
423
+ execFileMock.mockReset();
424
+ // preFlight: playwright --version succeeds
425
+ makeExecSuccess();
426
+ // lhci autorun succeeds
427
+ execFileMock.mockImplementationOnce(
428
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
429
+ cb(null, 'lhci done', '');
430
+ },
431
+ );
432
+ // pa11y-ci fails
433
+ execFileMock.mockImplementation(
434
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
435
+ cb(new Error('not found'), '', '');
436
+ },
437
+ );
438
+
439
+ const agent = createAgent();
440
+ const result = await agent.run();
441
+
442
+ const lhEvidence = result.evidence.find(e => e.description.includes('Lighthouse'));
443
+ expect(lhEvidence).toBeDefined();
444
+ });
445
+ });
446
+
447
+ // -------------------------------------------------------------------------
448
+ // Pa11y CI (optional)
449
+ // -------------------------------------------------------------------------
450
+
451
+ describe('Pa11y CI integration', () => {
452
+ it('adds report evidence when pa11y-ci succeeds', async () => {
453
+ execFileMock.mockReset();
454
+ // preFlight
455
+ makeExecSuccess();
456
+ // lhci fails
457
+ execFileMock.mockImplementationOnce(
458
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
459
+ cb(new Error('not found'), '', '');
460
+ },
461
+ );
462
+ // pa11y-ci succeeds
463
+ execFileMock.mockImplementationOnce(
464
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
465
+ cb(null, 'pa11y done', '');
466
+ },
467
+ );
468
+
469
+ const agent = createAgent();
470
+ const result = await agent.run();
471
+
472
+ const pa11yEvidence = result.evidence.find(e => e.description.includes('Pa11y'));
473
+ expect(pa11yEvidence).toBeDefined();
474
+ });
475
+ });
476
+
477
+ // -------------------------------------------------------------------------
478
+ // Violation help URL included in suggestedFix
479
+ // -------------------------------------------------------------------------
480
+
481
+ describe('violation suggestedFix', () => {
482
+ it('includes violation help text in suggestedFix field', async () => {
483
+ analyzeMock.mockResolvedValueOnce({
484
+ violations: [{
485
+ id: 'image-alt',
486
+ impact: 'serious',
487
+ description: 'Images must have alt text',
488
+ help: 'Ensure images have alternative text',
489
+ helpUrl: 'https://dequeuniversity.com/rules/image-alt',
490
+ nodes: [{ html: '<img src="logo.png">' }],
491
+ }],
492
+ });
493
+ analyzeMock.mockResolvedValueOnce({ violations: [] });
494
+
495
+ const agent = createAgent();
496
+ const result = await agent.run();
497
+
498
+ const imgFinding = result.findings.find(f => f.id.includes('image-alt'));
499
+ expect(imgFinding).toBeDefined();
500
+ expect(imgFinding!.suggestedFix).toBe('Ensure images have alternative text');
501
+ });
502
+ });
503
+
504
+ // -------------------------------------------------------------------------
505
+ // Missing config
506
+ // -------------------------------------------------------------------------
507
+
508
+ describe('missing config handling', () => {
509
+ it('fails with critical finding when no environment is configured', async () => {
510
+ const cfg: ValidatedConfig = { ...stubConfig, environments: {} };
511
+ const agent = new A11yGuardianAgent(cfg, 'beta', '/tmp/test-run');
512
+ const result = await agent.run();
513
+
514
+ expect(result.findings[0].severity).toBe('critical');
515
+ expect(result.findings[0].description).toContain('No environment');
516
+ });
517
+
518
+ it('fails with critical finding when no credentials are configured', async () => {
519
+ const cfg: ValidatedConfig = {
520
+ ...stubConfig,
521
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
522
+ };
523
+ const agent = new A11yGuardianAgent(cfg, 'beta', '/tmp/test-run');
524
+ const result = await agent.run();
525
+
526
+ expect(result.findings[0].severity).toBe('critical');
527
+ expect(result.findings[0].description).toContain('credentials');
528
+ });
529
+ });
530
+ });