@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,492 @@
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
+ { name: 'client', queryParam: 'client' },
24
+ ],
25
+ breakpoints: [1280, 768],
26
+ integrations: [],
27
+ workers: [],
28
+ workflows: [],
29
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
30
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
31
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
32
+ agentClassification: { blocking: [], advisory: [] },
33
+ thresholds: {
34
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
35
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
36
+ apiResponseTime: 2000,
37
+ bundleSizeLimit: 500000,
38
+ maxConsoleErrors: 0,
39
+ },
40
+ disabledAgents: [],
41
+ disabledAgentSet: new Set<number>(),
42
+ };
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Mock child_process (for preFlight playwright version check)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const spawnMock = vi.fn();
49
+ vi.mock('child_process', () => ({
50
+ execFile: (...args: unknown[]) => spawnMock(...args),
51
+ }));
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Mock fetch (for isUrlReachable in preFlight)
55
+ // ---------------------------------------------------------------------------
56
+
57
+ const fetchMock = vi.fn();
58
+ vi.stubGlobal('fetch', fetchMock);
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Mock helpers
62
+ // ---------------------------------------------------------------------------
63
+
64
+ const loginMock = vi.fn().mockResolvedValue(undefined);
65
+ const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
66
+ const waitForSectionLoadMock = vi.fn().mockResolvedValue(undefined);
67
+ const switchPortalMock = vi.fn().mockResolvedValue(undefined);
68
+ const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
69
+ const discoverInteractiveElementsMock = vi.fn().mockResolvedValue([]);
70
+ const fillFormMock = vi.fn().mockResolvedValue(undefined);
71
+ const openModalMock = vi.fn().mockResolvedValue(false);
72
+ const closeModalMock = vi.fn().mockResolvedValue(true);
73
+
74
+ vi.mock('../../helpers/navigation', () => ({
75
+ login: (...args: unknown[]) => loginMock(...args),
76
+ navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
77
+ waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
78
+ switchPortal: (...args: unknown[]) => switchPortalMock(...args),
79
+ }));
80
+
81
+ vi.mock('../../helpers/screenshot', () => ({
82
+ captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
83
+ }));
84
+
85
+ vi.mock('../../helpers/element-discovery', () => ({
86
+ discoverInteractiveElements: (...args: unknown[]) => discoverInteractiveElementsMock(...args),
87
+ }));
88
+
89
+ vi.mock('../../helpers/form-filler', () => ({
90
+ fillForm: (...args: unknown[]) => fillFormMock(...args),
91
+ }));
92
+
93
+ vi.mock('../../helpers/modal-handler', () => ({
94
+ openModal: (...args: unknown[]) => openModalMock(...args),
95
+ closeModal: (...args: unknown[]) => closeModalMock(...args),
96
+ }));
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Mock Playwright
100
+ // ---------------------------------------------------------------------------
101
+
102
+ type ConsoleListener = (msg: { type(): string; text(): string }) => void;
103
+
104
+ interface MockPage {
105
+ close: ReturnType<typeof vi.fn>;
106
+ on: ReturnType<typeof vi.fn>;
107
+ removeListener: ReturnType<typeof vi.fn>;
108
+ waitForTimeout: ReturnType<typeof vi.fn>;
109
+ setViewportSize: ReturnType<typeof vi.fn>;
110
+ _consoleListeners: ConsoleListener[];
111
+ emitConsole(type: string, text: string): void;
112
+ }
113
+
114
+ function makeMockPage(): MockPage {
115
+ const consoleListeners: ConsoleListener[] = [];
116
+
117
+ const page: MockPage = {
118
+ close: vi.fn().mockResolvedValue(undefined),
119
+ on: vi.fn().mockImplementation((event: string, listener: ConsoleListener) => {
120
+ if (event === 'console') consoleListeners.push(listener);
121
+ }),
122
+ removeListener: vi.fn().mockImplementation((event: string, listener: ConsoleListener) => {
123
+ if (event === 'console') {
124
+ const idx = consoleListeners.indexOf(listener);
125
+ if (idx !== -1) consoleListeners.splice(idx, 1);
126
+ }
127
+ }),
128
+ waitForTimeout: vi.fn().mockResolvedValue(undefined),
129
+ setViewportSize: vi.fn().mockResolvedValue(undefined),
130
+ _consoleListeners: consoleListeners,
131
+ emitConsole(type: string, text: string): void {
132
+ for (const l of consoleListeners) {
133
+ l({ type: () => type, text: () => text });
134
+ }
135
+ },
136
+ };
137
+
138
+ return page;
139
+ }
140
+
141
+ let currentMockPage: MockPage;
142
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
143
+
144
+ vi.mock('playwright', async () => ({
145
+ chromium: {
146
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
147
+ },
148
+ }));
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Dynamic import AFTER mocks are hoisted
152
+ // ---------------------------------------------------------------------------
153
+
154
+ const { BrowserCrawlerAgent } = await import('../05-browser-crawler');
155
+
156
+ function createAgent(): InstanceType<typeof BrowserCrawlerAgent> {
157
+ return new BrowserCrawlerAgent(stubConfig, 'beta', '/tmp/test-run');
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Tests
162
+ // ---------------------------------------------------------------------------
163
+
164
+ describe('BrowserCrawlerAgent — Beta mode', () => {
165
+ beforeEach(() => {
166
+ spawnMock.mockReset();
167
+ fetchMock.mockReset();
168
+ loginMock.mockReset().mockResolvedValue(undefined);
169
+ navigateToSectionMock.mockReset().mockResolvedValue(undefined);
170
+ waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
171
+ switchPortalMock.mockReset().mockResolvedValue(undefined);
172
+ captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
173
+ discoverInteractiveElementsMock.mockReset().mockResolvedValue([]);
174
+ fillFormMock.mockReset().mockResolvedValue(undefined);
175
+ openModalMock.mockReset().mockResolvedValue(false);
176
+ closeModalMock.mockReset().mockResolvedValue(true);
177
+
178
+ currentMockPage = makeMockPage();
179
+ mockBrowser = {
180
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
181
+ close: vi.fn().mockResolvedValue(undefined),
182
+ };
183
+
184
+ spawnMock.mockImplementation(
185
+ (_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
186
+ cb(null, '1.40.0\n', '');
187
+ },
188
+ );
189
+ fetchMock.mockResolvedValue({ ok: true, status: 200 });
190
+ });
191
+
192
+ it('enters beta mode and does not throw "not yet implemented"', async () => {
193
+ const agent = createAgent();
194
+ const result = await agent.run();
195
+
196
+ expect(result.findings.some(f => f.description.includes('not implemented'))).toBe(false);
197
+ });
198
+
199
+ it('calls login once per breakpoint', async () => {
200
+ const agent = createAgent();
201
+ await agent.run();
202
+
203
+ // Beta mode uses a single desktop breakpoint [1280] regardless of config
204
+ expect(loginMock).toHaveBeenCalledTimes(1);
205
+ });
206
+
207
+ it('calls navigateToSection for each module × each breakpoint pass', async () => {
208
+ const agent = createAgent();
209
+ await agent.run();
210
+
211
+ // 2 breakpoints × 2 modules = 4 bookkeeper calls, plus portal calls
212
+ // The bookkeeper pass alone is 2 × 2 = 4
213
+ const navigateCalls = navigateToSectionMock.mock.calls;
214
+ expect(navigateCalls.length).toBeGreaterThanOrEqual(
215
+ stubConfig.breakpoints.length * stubConfig.modules.length,
216
+ );
217
+ });
218
+
219
+ it('calls discoverInteractiveElements for each module visited', async () => {
220
+ const agent = createAgent();
221
+ await agent.run();
222
+
223
+ expect(discoverInteractiveElementsMock).toHaveBeenCalled();
224
+ });
225
+
226
+ it('takes a before and after screenshot for each module', async () => {
227
+ const agent = createAgent();
228
+ const result = await agent.run();
229
+
230
+ const screenshotEvidence = result.evidence.filter(e => e.type === 'screenshot');
231
+ // 2 breakpoints × 2 portals-passes × 2 modules × 2 (before+after) = at least 8
232
+ expect(screenshotEvidence.length).toBeGreaterThan(0);
233
+ });
234
+
235
+ it('skips destructive elements and does not attempt to click them', async () => {
236
+ discoverInteractiveElementsMock.mockResolvedValue([
237
+ {
238
+ selector: '[data-testid="delete-btn"]',
239
+ type: 'button',
240
+ text: 'Delete',
241
+ isDestructive: true,
242
+ },
243
+ {
244
+ selector: '[data-testid="save-btn"]',
245
+ type: 'button',
246
+ text: 'Save',
247
+ isDestructive: false,
248
+ },
249
+ ]);
250
+
251
+ const agent = createAgent();
252
+ await agent.run();
253
+
254
+ // openModal is called for non-destructive buttons; delete button must be skipped
255
+ const openModalCalls = openModalMock.mock.calls as Array<[unknown, string]>;
256
+ const clickedSelectors = openModalCalls.map(([, sel]) => sel);
257
+ expect(clickedSelectors).not.toContain('[data-testid="delete-btn"]');
258
+ });
259
+
260
+ it('interacts with non-destructive button elements via openModal', async () => {
261
+ discoverInteractiveElementsMock.mockResolvedValue([
262
+ {
263
+ selector: '[data-testid="new-invoice"]',
264
+ type: 'button',
265
+ text: 'New Invoice',
266
+ isDestructive: false,
267
+ },
268
+ ]);
269
+
270
+ const agent = createAgent();
271
+ await agent.run();
272
+
273
+ expect(openModalMock).toHaveBeenCalledWith(currentMockPage, '[data-testid="new-invoice"]');
274
+ });
275
+
276
+ it('calls closeModal when openModal returns true (modal was opened)', async () => {
277
+ discoverInteractiveElementsMock.mockResolvedValue([
278
+ {
279
+ selector: '[data-testid="open-modal"]',
280
+ type: 'button',
281
+ text: 'Open',
282
+ isDestructive: false,
283
+ },
284
+ ]);
285
+ openModalMock.mockResolvedValue(true);
286
+
287
+ const agent = createAgent();
288
+ await agent.run();
289
+
290
+ expect(closeModalMock).toHaveBeenCalled();
291
+ });
292
+
293
+ it('creates a console-error finding when a console error fires during interaction', async () => {
294
+ navigateToSectionMock.mockImplementation(
295
+ async (_page: unknown, moduleId: string) => {
296
+ if (moduleId === 'dashboard') {
297
+ currentMockPage.emitConsole('error', 'Uncaught ReferenceError: foo');
298
+ }
299
+ },
300
+ );
301
+
302
+ const agent = createAgent();
303
+ const result = await agent.run();
304
+
305
+ const consoleFinding = result.findings.find(f => f.consoleError !== undefined);
306
+ expect(consoleFinding).toBeDefined();
307
+ expect(consoleFinding!.severity).toBe('high');
308
+ expect(consoleFinding!.consoleError).toBe('Uncaught ReferenceError: foo');
309
+ });
310
+
311
+ it('calls switchPortal for configured portal passes', async () => {
312
+ const agent = createAgent();
313
+ await agent.run();
314
+
315
+ // Beta mode uses a single desktop breakpoint, so 1 portal pass
316
+ expect(switchPortalMock).toHaveBeenCalledTimes(1);
317
+ expect(switchPortalMock).toHaveBeenCalledWith(currentMockPage, 'client');
318
+ });
319
+
320
+ it('returns passed status when no errors are found', async () => {
321
+ const agent = createAgent();
322
+ const result = await agent.run();
323
+
324
+ expect(result.status).toBe('passed');
325
+ expect(result.findings).toHaveLength(0);
326
+ });
327
+
328
+ it('uses default breakpoint [1280] when breakpoints array is empty', async () => {
329
+ const configNoBreakpoints: ValidatedConfig = {
330
+ ...stubConfig,
331
+ breakpoints: [],
332
+ portals: [],
333
+ };
334
+ const agent = new BrowserCrawlerAgent(configNoBreakpoints, 'beta', '/tmp/test-run');
335
+ await agent.run();
336
+
337
+ // Should still call login once (for the single default-breakpoint pass)
338
+ expect(loginMock).toHaveBeenCalledTimes(1);
339
+ });
340
+
341
+ // -------------------------------------------------------------------------
342
+ // Beta: no environment configured
343
+ // -------------------------------------------------------------------------
344
+
345
+ it('handles empty environments gracefully via resolveEnvironment fallback', async () => {
346
+ const configNoEnv: ValidatedConfig = {
347
+ ...stubConfig,
348
+ environments: {},
349
+ };
350
+ // resolveEnvironment will throw or return a fallback — preFlight catches it
351
+ fetchMock.mockRejectedValue(new Error('no env'));
352
+ const agent = new BrowserCrawlerAgent(configNoEnv, 'beta', '/tmp/test-run');
353
+ const result = await agent.run();
354
+
355
+ // Either fails in preFlight or returns critical finding
356
+ expect(result.status === 'failed' || result.findings.some(f => f.severity === 'critical')).toBe(true);
357
+ });
358
+
359
+ // -------------------------------------------------------------------------
360
+ // Beta: no credentials configured
361
+ // -------------------------------------------------------------------------
362
+
363
+ it('creates critical finding when no credentials are configured', async () => {
364
+ const configNoCreds: ValidatedConfig = {
365
+ ...stubConfig,
366
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
367
+ };
368
+ const agent = new BrowserCrawlerAgent(configNoCreds, 'beta', '/tmp/test-run');
369
+ const result = await agent.run();
370
+
371
+ expect(result.status).toBe('failed');
372
+ const finding = result.findings.find(f => f.id === '5-beta-no-credentials');
373
+ expect(finding).toBeDefined();
374
+ expect(finding!.severity).toBe('critical');
375
+ });
376
+
377
+ // -------------------------------------------------------------------------
378
+ // Beta: login failure with fallback severity
379
+ // -------------------------------------------------------------------------
380
+
381
+ it('creates a login finding when login throws in beta mode', async () => {
382
+ loginMock.mockRejectedValue(new Error('Login failed'));
383
+ // Beta mode needs page.goto for fallback anonymous crawling
384
+ (currentMockPage as unknown as Record<string, unknown>).goto = vi.fn().mockResolvedValue(undefined);
385
+
386
+ const agent = createAgent();
387
+ const result = await agent.run();
388
+
389
+ // Login failure in beta produces a finding that mentions "Login failed"
390
+ const loginFinding = result.findings.find(f =>
391
+ f.description.includes('Login failed') || f.id.includes('login-failed'),
392
+ );
393
+ expect(loginFinding).toBeDefined();
394
+ });
395
+
396
+ // -------------------------------------------------------------------------
397
+ // Beta: navigation timeout in interaction pass
398
+ // -------------------------------------------------------------------------
399
+
400
+ it('creates a medium finding for navigation timeout (5s)', async () => {
401
+ navigateToSectionMock.mockImplementation(async () => {
402
+ return new Promise((_resolve, reject) =>
403
+ setTimeout(() => reject(new Error('Navigation timed out after 5s')), 10),
404
+ );
405
+ });
406
+
407
+ const agent = createAgent();
408
+ const result = await agent.run();
409
+
410
+ const timeoutFinding = result.findings.find(f =>
411
+ f.description.includes('exceeded 5s timeout'),
412
+ );
413
+ expect(timeoutFinding).toBeDefined();
414
+ expect(timeoutFinding!.severity).toBe('medium');
415
+ });
416
+
417
+ it('creates a high finding for non-timeout navigation failure', async () => {
418
+ navigateToSectionMock.mockRejectedValue(new Error('Element not found'));
419
+
420
+ const agent = createAgent();
421
+ const result = await agent.run();
422
+
423
+ const navFinding = result.findings.find(f =>
424
+ f.id.includes('beta-nav') && f.severity === 'high',
425
+ );
426
+ expect(navFinding).toBeDefined();
427
+ });
428
+
429
+ // -------------------------------------------------------------------------
430
+ // Beta: input/textarea/select element interaction via fillForm
431
+ // -------------------------------------------------------------------------
432
+
433
+ it('calls fillForm for input elements', async () => {
434
+ discoverInteractiveElementsMock.mockResolvedValue([
435
+ {
436
+ selector: 'input[name="email"]',
437
+ type: 'input',
438
+ text: '',
439
+ isDestructive: false,
440
+ },
441
+ ]);
442
+
443
+ const agent = createAgent();
444
+ await agent.run();
445
+
446
+ expect(fillFormMock).toHaveBeenCalled();
447
+ });
448
+
449
+ // -------------------------------------------------------------------------
450
+ // Beta: modal opened then closed via waitForTimeout
451
+ // -------------------------------------------------------------------------
452
+
453
+ it('waits after interaction when openModal returns false (no modal)', async () => {
454
+ discoverInteractiveElementsMock.mockResolvedValue([
455
+ {
456
+ selector: '[data-testid="tab-1"]',
457
+ type: 'tab',
458
+ text: 'Tab 1',
459
+ isDestructive: false,
460
+ },
461
+ ]);
462
+ openModalMock.mockResolvedValue(false);
463
+
464
+ const agent = createAgent();
465
+ await agent.run();
466
+
467
+ expect(currentMockPage.waitForTimeout).toHaveBeenCalledWith(1000);
468
+ });
469
+
470
+ // -------------------------------------------------------------------------
471
+ // Beta: limits to first 5 modules
472
+ // -------------------------------------------------------------------------
473
+
474
+ it('limits interaction pass to first 5 modules in beta', async () => {
475
+ const manyModules = Array.from({ length: 8 }, (_, i) => ({
476
+ id: `mod-${i}`,
477
+ route: `/mod-${i}`,
478
+ sidebarIcon: true,
479
+ }));
480
+ const configManyModules: ValidatedConfig = {
481
+ ...stubConfig,
482
+ modules: manyModules,
483
+ portals: [],
484
+ };
485
+ const agent = new BrowserCrawlerAgent(configManyModules, 'beta', '/tmp/test-run');
486
+ await agent.run();
487
+
488
+ // Beta limits to 5 modules for the interaction pass
489
+ // navigateToSection is called once per module in the interaction pass
490
+ expect(navigateToSectionMock.mock.calls.length).toBeLessThanOrEqual(5);
491
+ });
492
+ });