@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,586 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { WorkflowConfig } from '../../core/config';
4
+ import type { Phase } from '../../core/types';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Stub config helpers
8
+ // ---------------------------------------------------------------------------
9
+
10
+ function makeWorkflow(name: string, steps: WorkflowConfig['steps'] = []): WorkflowConfig {
11
+ return { name, steps };
12
+ }
13
+
14
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
15
+ return {
16
+ schemaVersion: 1,
17
+ name: 'test',
18
+ auth: {
19
+ loginUrl: 'http://localhost/login',
20
+ credentials: {
21
+ admin: { email: 'admin@test.com', password: 'secret' },
22
+ },
23
+ },
24
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
25
+ environments: {
26
+ alpha: { baseUrl: 'http://localhost:3000', seed: false },
27
+ },
28
+ portals: [],
29
+ breakpoints: [],
30
+ integrations: [],
31
+ workers: [],
32
+ workflows: [
33
+ makeWorkflow('onboarding', [
34
+ { action: 'navigate', target: '/onboarding' },
35
+ { action: 'click', target: '[data-testid="next"]' },
36
+ { action: 'verify', target: '[data-testid="step-2"]', contains: 'Step 2' },
37
+ ]),
38
+ ],
39
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
40
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
41
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
42
+ agentClassification: { blocking: [], advisory: [] },
43
+ thresholds: {
44
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
45
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
46
+ apiResponseTime: 2000,
47
+ bundleSizeLimit: 500000,
48
+ maxConsoleErrors: 0,
49
+ },
50
+ disabledAgents: [],
51
+ disabledAgentSet: new Set<number>(),
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Mock fetch (preFlight URL reachability)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ const fetchMock = vi.fn();
61
+ vi.stubGlobal('fetch', fetchMock);
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Mock helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ const loginMock = vi.fn().mockResolvedValue(undefined);
68
+ const waitForSectionLoadMock = vi.fn().mockResolvedValue(undefined);
69
+ const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
70
+ const fillFormMock = vi.fn().mockResolvedValue(undefined);
71
+
72
+ vi.mock('../../helpers/navigation', () => ({
73
+ login: (...args: unknown[]) => loginMock(...args),
74
+ waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
75
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
76
+ switchPortal: vi.fn().mockResolvedValue(undefined),
77
+ }));
78
+
79
+ vi.mock('../../helpers/screenshot', () => ({
80
+ captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
81
+ }));
82
+
83
+ vi.mock('../../helpers/form-filler', () => ({
84
+ fillForm: (...args: unknown[]) => fillFormMock(...args),
85
+ }));
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Mock Playwright
89
+ // ---------------------------------------------------------------------------
90
+
91
+ interface MockElement {
92
+ textContent: ReturnType<typeof vi.fn>;
93
+ }
94
+
95
+ interface MockPage {
96
+ close: ReturnType<typeof vi.fn>;
97
+ goto: ReturnType<typeof vi.fn>;
98
+ click: ReturnType<typeof vi.fn>;
99
+ waitForSelector: ReturnType<typeof vi.fn>;
100
+ setViewportSize: ReturnType<typeof vi.fn>;
101
+ _resolveWaitForSelector: (selector: string, resolvedText: string) => void;
102
+ }
103
+
104
+ function makeMockPage(overrides: Partial<MockPage> = {}): MockPage {
105
+ const waitForSelectorMap = new Map<string, string>();
106
+
107
+ const page: MockPage = {
108
+ close: vi.fn().mockResolvedValue(undefined),
109
+ goto: vi.fn().mockResolvedValue(undefined),
110
+ click: vi.fn().mockResolvedValue(undefined),
111
+ waitForSelector: vi.fn().mockImplementation(async (selector: string) => {
112
+ const text = waitForSelectorMap.get(selector) ?? '';
113
+ const element: MockElement = {
114
+ textContent: vi.fn().mockResolvedValue(text),
115
+ };
116
+ return element;
117
+ }),
118
+ setViewportSize: vi.fn().mockResolvedValue(undefined),
119
+ _resolveWaitForSelector(selector: string, text: string) {
120
+ waitForSelectorMap.set(selector, text);
121
+ },
122
+ ...overrides,
123
+ };
124
+
125
+ return page;
126
+ }
127
+
128
+ let currentMockPage: MockPage;
129
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
130
+
131
+ vi.mock('playwright', async () => ({
132
+ chromium: {
133
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
134
+ },
135
+ }));
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Dynamic import AFTER mocks are hoisted
139
+ // ---------------------------------------------------------------------------
140
+
141
+ const { WorkflowCompletionTesterAgent } = await import('../20-workflow-completion-tester');
142
+
143
+ function createAgent(
144
+ config: ValidatedConfig = makeConfig(),
145
+ phase: Phase = 'uat',
146
+ ): InstanceType<typeof WorkflowCompletionTesterAgent> {
147
+ return new WorkflowCompletionTesterAgent(config, phase, '/tmp/test-run');
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Tests
152
+ // ---------------------------------------------------------------------------
153
+
154
+ describe('WorkflowCompletionTesterAgent', () => {
155
+ beforeEach(() => {
156
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
157
+ loginMock.mockReset().mockResolvedValue(undefined);
158
+ waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
159
+ captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
160
+ fillFormMock.mockReset().mockResolvedValue(undefined);
161
+
162
+ currentMockPage = makeMockPage();
163
+ mockBrowser = {
164
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
165
+ close: vi.fn().mockResolvedValue(undefined),
166
+ };
167
+ });
168
+
169
+ // -------------------------------------------------------------------------
170
+ // Identity
171
+ // -------------------------------------------------------------------------
172
+
173
+ it('has agentId 20 and agentName "Workflow Completion Tester"', () => {
174
+ const agent = createAgent();
175
+ expect(agent.agentId).toBe(20);
176
+ expect(agent.agentName).toBe('Workflow Completion Tester');
177
+ });
178
+
179
+ // -------------------------------------------------------------------------
180
+ // preFlight()
181
+ // -------------------------------------------------------------------------
182
+
183
+ describe('preFlight()', () => {
184
+ it('throws when workflows array is empty', async () => {
185
+ const config = makeConfig({ workflows: [] });
186
+ const agent = createAgent(config);
187
+ const result = await agent.run();
188
+
189
+ expect(result.status).toBe('failed');
190
+ expect(result.findings[0].description).toContain('workflow');
191
+ });
192
+
193
+ it('resolves gracefully when base URL is not reachable (fallback)', async () => {
194
+ fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
195
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
196
+ const agent = createAgent();
197
+ const result = await agent.run();
198
+
199
+ // With resolveEnvironment the agent no longer throws on unreachable URLs.
200
+ // preFlight succeeds; execute proceeds with the fallback environment.
201
+ expect(result.findings.some((f) => f.description.includes('not reachable'))).toBe(false);
202
+ });
203
+
204
+ it('passes when workflows are configured and base URL is reachable', async () => {
205
+ const agent = createAgent();
206
+ const result = await agent.run();
207
+
208
+ expect(result.findings.some(f => f.description.includes('workflow'))).toBe(false);
209
+ });
210
+ });
211
+
212
+ // -------------------------------------------------------------------------
213
+ // Successful workflow execution
214
+ // -------------------------------------------------------------------------
215
+
216
+ describe('execute() — successful workflow', () => {
217
+ it('returns no findings when all workflow steps succeed', async () => {
218
+ // Set expected text for verify step
219
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2 content');
220
+
221
+ const agent = createAgent();
222
+ const result = await agent.run();
223
+
224
+ expect(result.findings).toHaveLength(0);
225
+ });
226
+
227
+ it('calls page.goto for navigate steps', async () => {
228
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
229
+
230
+ const agent = createAgent();
231
+ await agent.run();
232
+
233
+ expect(currentMockPage.goto).toHaveBeenCalledWith('http://localhost:3000/onboarding');
234
+ });
235
+
236
+ it('calls page.click for click steps', async () => {
237
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
238
+
239
+ const agent = createAgent();
240
+ await agent.run();
241
+
242
+ expect(currentMockPage.click).toHaveBeenCalledWith('[data-testid="next"]');
243
+ });
244
+
245
+ it('takes a screenshot at each step as evidence', async () => {
246
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
247
+
248
+ const agent = createAgent();
249
+ const result = await agent.run();
250
+
251
+ // 3 steps in onboarding workflow → 3 screenshots
252
+ const screenshotEvidence = result.evidence.filter(e => e.type === 'screenshot');
253
+ expect(screenshotEvidence.length).toBeGreaterThanOrEqual(3);
254
+ });
255
+ });
256
+
257
+ // -------------------------------------------------------------------------
258
+ // Failed step creates finding with workflow name
259
+ // -------------------------------------------------------------------------
260
+
261
+ describe('execute() — failed step', () => {
262
+ it('creates a finding with the workflow name when a step fails', async () => {
263
+ // click step throws
264
+ currentMockPage.click.mockRejectedValue(new Error('Element not found'));
265
+
266
+ const agent = createAgent();
267
+ const result = await agent.run();
268
+
269
+ const workflowFinding = result.findings.find(
270
+ f => f.module.includes('onboarding'),
271
+ );
272
+ expect(workflowFinding).toBeDefined();
273
+ expect(workflowFinding!.description).toContain('onboarding');
274
+ });
275
+
276
+ it('creates a finding that includes the step index', async () => {
277
+ currentMockPage.click.mockRejectedValue(new Error('Selector timeout'));
278
+
279
+ const agent = createAgent();
280
+ const result = await agent.run();
281
+
282
+ const finding = result.findings[0];
283
+ // Step index 1 is the click step (0-indexed)
284
+ expect(finding.description).toContain('step 1');
285
+ });
286
+
287
+ it('captures a failure screenshot when a step fails', async () => {
288
+ currentMockPage.click.mockRejectedValue(new Error('Click failed'));
289
+
290
+ const agent = createAgent();
291
+ const result = await agent.run();
292
+
293
+ const finding = result.findings[0];
294
+ expect(finding.screenshot).toBeDefined();
295
+ });
296
+
297
+ it('stops processing the workflow after the first failed step', async () => {
298
+ // Navigate (step 0) succeeds; click (step 1) fails
299
+ currentMockPage.click.mockRejectedValue(new Error('Click failed'));
300
+
301
+ const agent = createAgent();
302
+ await agent.run();
303
+
304
+ // waitForSelector for verify (step 2) should NOT have been called
305
+ const verifyCall = (currentMockPage.waitForSelector.mock.calls as Array<unknown[]>).find(
306
+ (c) => c[0] === '[data-testid="step-2"]',
307
+ );
308
+ expect(verifyCall).toBeUndefined();
309
+ });
310
+ });
311
+
312
+ // -------------------------------------------------------------------------
313
+ // verify action checks text content
314
+ // -------------------------------------------------------------------------
315
+
316
+ describe('execute() — verify action', () => {
317
+ it('creates no finding when element contains expected text', async () => {
318
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2 content here');
319
+
320
+ const agent = createAgent();
321
+ const result = await agent.run();
322
+
323
+ expect(result.findings).toHaveLength(0);
324
+ });
325
+
326
+ it('creates a finding when element does not contain expected text', async () => {
327
+ // waitForSelector returns element but textContent does not match
328
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Wrong content');
329
+
330
+ const agent = createAgent();
331
+ const result = await agent.run();
332
+
333
+ const verifyFinding = result.findings.find(
334
+ f => f.description.includes('does not contain'),
335
+ );
336
+ expect(verifyFinding).toBeDefined();
337
+ });
338
+
339
+ it('creates a finding that mentions the expected text when mismatch', async () => {
340
+ currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Something else');
341
+
342
+ const agent = createAgent();
343
+ const result = await agent.run();
344
+
345
+ expect(result.findings[0].description).toContain('Step 2');
346
+ });
347
+ });
348
+
349
+ // -------------------------------------------------------------------------
350
+ // fill-form action
351
+ // -------------------------------------------------------------------------
352
+
353
+ describe('execute() — fill-form action', () => {
354
+ it('calls fillForm helper for fill-form steps', async () => {
355
+ const config = makeConfig({
356
+ workflows: [
357
+ {
358
+ name: 'create-invoice',
359
+ steps: [
360
+ { action: 'fill-form', target: 'form[data-form="invoice"]', data: 'test-data' },
361
+ ],
362
+ },
363
+ ],
364
+ });
365
+
366
+ const agent = createAgent(config);
367
+ await agent.run();
368
+
369
+ expect(fillFormMock).toHaveBeenCalledWith(
370
+ currentMockPage,
371
+ 'form[data-form="invoice"]',
372
+ { data: 'test-data' },
373
+ );
374
+ });
375
+ });
376
+
377
+ // -------------------------------------------------------------------------
378
+ // wait action
379
+ // -------------------------------------------------------------------------
380
+
381
+ describe('execute() — wait action', () => {
382
+ it('calls waitForSelector for wait steps', async () => {
383
+ const config = makeConfig({
384
+ workflows: [
385
+ {
386
+ name: 'loading-test',
387
+ steps: [
388
+ { action: 'wait', target: '[data-testid="spinner-gone"]', timeout: 3000 },
389
+ ],
390
+ },
391
+ ],
392
+ });
393
+
394
+ const agent = createAgent(config);
395
+ await agent.run();
396
+
397
+ expect(currentMockPage.waitForSelector).toHaveBeenCalledWith(
398
+ '[data-testid="spinner-gone"]',
399
+ { timeout: 3000 },
400
+ );
401
+ });
402
+ });
403
+
404
+ // -------------------------------------------------------------------------
405
+ // Multiple workflows
406
+ // -------------------------------------------------------------------------
407
+
408
+ describe('execute() — multiple workflows', () => {
409
+ it('runs all workflows and accumulates findings', async () => {
410
+ const config = makeConfig({
411
+ workflows: [
412
+ {
413
+ name: 'workflow-a',
414
+ steps: [{ action: 'navigate', target: '/a' }],
415
+ },
416
+ {
417
+ name: 'workflow-b',
418
+ steps: [{ action: 'click', target: '[broken]' }],
419
+ },
420
+ ],
421
+ });
422
+
423
+ // navigate succeeds; click fails
424
+ currentMockPage.click.mockRejectedValue(new Error('Not found'));
425
+
426
+ const agent = createAgent(config);
427
+ const result = await agent.run();
428
+
429
+ // workflow-a passes, workflow-b fails
430
+ const wfBFinding = result.findings.find(f => f.module.includes('workflow-b'));
431
+ expect(wfBFinding).toBeDefined();
432
+ // workflow-a should NOT produce a finding
433
+ const wfAFinding = result.findings.find(f => f.module.includes('workflow-a'));
434
+ expect(wfAFinding).toBeUndefined();
435
+ });
436
+ });
437
+
438
+ // -------------------------------------------------------------------------
439
+ // No credentials
440
+ // -------------------------------------------------------------------------
441
+
442
+ describe('execute() — no credentials', () => {
443
+ it('creates a critical finding when no credentials are configured', async () => {
444
+ const config = makeConfig({
445
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
446
+ });
447
+ const agent = createAgent(config);
448
+ const result = await agent.run();
449
+
450
+ const finding = result.findings.find(f => f.id.includes('no-credentials'));
451
+ expect(finding).toBeDefined();
452
+ expect(finding!.severity).toBe('critical');
453
+ });
454
+ });
455
+
456
+ // -------------------------------------------------------------------------
457
+ // Login failure
458
+ // -------------------------------------------------------------------------
459
+
460
+ describe('execute() — login failure', () => {
461
+ it('creates a medium finding and skips workflows when login fails', async () => {
462
+ loginMock.mockRejectedValue(new Error('Invalid credentials'));
463
+
464
+ const agent = createAgent();
465
+ const result = await agent.run();
466
+
467
+ const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
468
+ expect(loginFinding).toBeDefined();
469
+ expect(loginFinding!.severity).toBe('medium');
470
+ });
471
+ });
472
+
473
+ // -------------------------------------------------------------------------
474
+ // Playwright crash
475
+ // -------------------------------------------------------------------------
476
+
477
+ describe('execute() — playwright crash', () => {
478
+ it('creates a medium finding when browser launch throws', async () => {
479
+ mockBrowser.newPage.mockRejectedValue(new Error('Browser not found'));
480
+
481
+ const agent = createAgent();
482
+ const result = await agent.run();
483
+
484
+ const playwrightFinding = result.findings.find(f => f.id.includes('playwright-failure'));
485
+ expect(playwrightFinding).toBeDefined();
486
+ expect(playwrightFinding!.severity).toBe('medium');
487
+ });
488
+ });
489
+
490
+ // -------------------------------------------------------------------------
491
+ // Navigate with absolute URL
492
+ // -------------------------------------------------------------------------
493
+
494
+ describe('execute() — navigate with http URL', () => {
495
+ it('uses absolute URL directly when target starts with http', async () => {
496
+ const config = makeConfig({
497
+ workflows: [
498
+ {
499
+ name: 'external-nav',
500
+ steps: [{ action: 'navigate', target: 'http://external.com/page' }],
501
+ },
502
+ ],
503
+ });
504
+
505
+ const agent = createAgent(config);
506
+ await agent.run();
507
+
508
+ expect(currentMockPage.goto).toHaveBeenCalledWith('http://external.com/page');
509
+ });
510
+ });
511
+
512
+ // -------------------------------------------------------------------------
513
+ // Click step with verify selector
514
+ // -------------------------------------------------------------------------
515
+
516
+ describe('execute() — click with verify', () => {
517
+ it('waits for verify selector after click', async () => {
518
+ const config = makeConfig({
519
+ workflows: [
520
+ {
521
+ name: 'verify-after-click',
522
+ steps: [
523
+ { action: 'click', target: '[data-testid="btn"]', verify: '[data-testid="result"]', timeout: 3000 },
524
+ ],
525
+ },
526
+ ],
527
+ });
528
+ currentMockPage._resolveWaitForSelector('[data-testid="result"]', 'Done');
529
+
530
+ const agent = createAgent(config);
531
+ await agent.run();
532
+
533
+ expect(currentMockPage.waitForSelector).toHaveBeenCalledWith(
534
+ '[data-testid="result"]',
535
+ { timeout: 3000 },
536
+ );
537
+ });
538
+ });
539
+
540
+ // -------------------------------------------------------------------------
541
+ // fill-form without data
542
+ // -------------------------------------------------------------------------
543
+
544
+ describe('execute() — fill-form without data', () => {
545
+ it('calls fillForm with undefined data when step has no data', async () => {
546
+ const config = makeConfig({
547
+ workflows: [
548
+ {
549
+ name: 'empty-form',
550
+ steps: [{ action: 'fill-form', target: 'form' }],
551
+ },
552
+ ],
553
+ });
554
+
555
+ const agent = createAgent(config);
556
+ await agent.run();
557
+
558
+ expect(fillFormMock).toHaveBeenCalledWith(currentMockPage, 'form', undefined);
559
+ });
560
+ });
561
+
562
+ // -------------------------------------------------------------------------
563
+ // Screenshot capture failure during success path
564
+ // -------------------------------------------------------------------------
565
+
566
+ describe('execute() — screenshot failure on success', () => {
567
+ it('does not add screenshot evidence when captureScreenshot fails', async () => {
568
+ captureScreenshotMock.mockResolvedValue('');
569
+
570
+ const config = makeConfig({
571
+ workflows: [
572
+ {
573
+ name: 'screenshot-fail',
574
+ steps: [{ action: 'navigate', target: '/page' }],
575
+ },
576
+ ],
577
+ });
578
+
579
+ const agent = createAgent(config);
580
+ const result = await agent.run();
581
+
582
+ // With empty string path, evidence should not be added (or be empty)
583
+ expect(result.findings).toHaveLength(0);
584
+ });
585
+ });
586
+ });