@avi770/testteam 1.2.0 → 2.0.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 (300) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +53 -6
  3. package/agents/24-signup-onboarding-tester.ts +429 -0
  4. package/agents/25-crud-flow-tester.ts +302 -0
  5. package/agents/26-form-validator.ts +297 -0
  6. package/agents/27-search-filter-tester.ts +326 -0
  7. package/agents/28-navigation-routing-tester.ts +425 -0
  8. package/agents/29-responsive-interaction-tester.ts +350 -0
  9. package/agents/30-multi-user-scenario-tester.ts +319 -0
  10. package/agents/31-load-tester.ts +134 -0
  11. package/agents/32-memory-leak-detector.ts +194 -0
  12. package/agents/33-bundle-analyzer.ts +132 -0
  13. package/agents/34-xss-scanner.ts +191 -0
  14. package/agents/35-csrf-tester.ts +82 -0
  15. package/agents/36-auth-fuzzer.ts +194 -0
  16. package/agents/37-dependency-scanner.ts +176 -0
  17. package/agents/38-secrets-scanner.ts +137 -0
  18. package/agents/39-api-contract-tester.ts +199 -0
  19. package/agents/40-rate-limit-tester.ts +94 -0
  20. package/agents/41-api-pagination-tester.ts +97 -0
  21. package/agents/42-graphql-tester.ts +222 -0
  22. package/agents/43-data-consistency-checker.ts +205 -0
  23. package/agents/44-backup-recovery-tester.ts +152 -0
  24. package/agents/45-data-privacy-scanner.ts +125 -0
  25. package/agents/46-seo-auditor.ts +294 -0
  26. package/agents/47-social-preview-tester.ts +232 -0
  27. package/agents/48-lighthouse-auditor.ts +213 -0
  28. package/agents/49-i18n-tester.ts +198 -0
  29. package/agents/50-timezone-tester.ts +173 -0
  30. package/agents/51-error-recovery-tester.ts +155 -0
  31. package/agents/52-offline-mode-tester.ts +180 -0
  32. package/agents/53-graceful-degradation-tester.ts +156 -0
  33. package/agents/54-websocket-tester.ts +151 -0
  34. package/agents/55-realtime-sync-tester.ts +194 -0
  35. package/agents/56-file-upload-tester.ts +194 -0
  36. package/agents/57-export-tester.ts +174 -0
  37. package/agents/58-payment-flow-tester.ts +183 -0
  38. package/agents/59-ssl-tls-auditor.ts +141 -0
  39. package/agents/60-dns-cdn-tester.ts +117 -0
  40. package/agents/61-docker-health-checker.ts +111 -0
  41. package/agents/62-env-config-validator.ts +152 -0
  42. package/agents/63-log-quality-auditor.ts +136 -0
  43. package/agents/64-analytics-tracker-tester.ts +165 -0
  44. package/agents/65-gdpr-compliance-tester.ts +215 -0
  45. package/agents/66-soc2-control-validator.ts +210 -0
  46. package/agents/67-wcag-aaa-tester.ts +241 -0
  47. package/agents/68-dead-code-detector.ts +135 -0
  48. package/agents/69-type-safety-auditor.ts +164 -0
  49. package/agents/70-complexity-analyzer.ts +179 -0
  50. package/agents/__tests__/24-signup-onboarding-tester.test.ts +274 -0
  51. package/agents/__tests__/25-crud-flow-tester.test.ts +322 -0
  52. package/agents/__tests__/26-form-validator.test.ts +345 -0
  53. package/agents/__tests__/27-search-filter-tester.test.ts +311 -0
  54. package/agents/__tests__/28-navigation-routing-tester.test.ts +328 -0
  55. package/agents/__tests__/29-responsive-interaction-tester.test.ts +297 -0
  56. package/agents/__tests__/30-multi-user-scenario-tester.test.ts +328 -0
  57. package/agents/__tests__/31-load-tester.test.ts +189 -0
  58. package/agents/__tests__/32-memory-leak-detector.test.ts +251 -0
  59. package/agents/__tests__/33-bundle-analyzer.test.ts +237 -0
  60. package/agents/__tests__/34-xss-scanner.test.ts +258 -0
  61. package/agents/__tests__/35-csrf-tester.test.ts +200 -0
  62. package/agents/__tests__/36-auth-fuzzer.test.ts +214 -0
  63. package/agents/__tests__/37-dependency-scanner.test.ts +266 -0
  64. package/agents/__tests__/38-secrets-scanner.test.ts +224 -0
  65. package/agents/__tests__/39-api-contract-tester.test.ts +312 -0
  66. package/agents/__tests__/40-rate-limit-tester.test.ts +192 -0
  67. package/agents/__tests__/41-api-pagination-tester.test.ts +198 -0
  68. package/agents/__tests__/42-graphql-tester.test.ts +252 -0
  69. package/agents/__tests__/43-data-consistency-checker.test.ts +232 -0
  70. package/agents/__tests__/44-backup-recovery-tester.test.ts +222 -0
  71. package/agents/__tests__/45-data-privacy-scanner.test.ts +223 -0
  72. package/agents/__tests__/46-seo-auditor.test.ts +261 -0
  73. package/agents/__tests__/47-social-preview-tester.test.ts +245 -0
  74. package/agents/__tests__/48-lighthouse-auditor.test.ts +276 -0
  75. package/agents/__tests__/49-i18n-tester.test.ts +201 -0
  76. package/agents/__tests__/50-timezone-tester.test.ts +172 -0
  77. package/agents/__tests__/51-error-recovery-tester.test.ts +162 -0
  78. package/agents/__tests__/52-offline-mode-tester.test.ts +164 -0
  79. package/agents/__tests__/53-graceful-degradation-tester.test.ts +168 -0
  80. package/agents/__tests__/54-websocket-tester.test.ts +157 -0
  81. package/agents/__tests__/55-realtime-sync-tester.test.ts +181 -0
  82. package/agents/__tests__/56-file-upload-tester.test.ts +172 -0
  83. package/agents/__tests__/57-export-tester.test.ts +169 -0
  84. package/agents/__tests__/58-payment-flow-tester.test.ts +182 -0
  85. package/agents/__tests__/59-ssl-tls-auditor.test.ts +179 -0
  86. package/agents/__tests__/60-dns-cdn-tester.test.ts +176 -0
  87. package/agents/__tests__/61-docker-health-checker.test.ts +150 -0
  88. package/agents/__tests__/62-env-config-validator.test.ts +166 -0
  89. package/agents/__tests__/63-log-quality-auditor.test.ts +175 -0
  90. package/agents/__tests__/64-analytics-tracker-tester.test.ts +158 -0
  91. package/agents/__tests__/65-gdpr-compliance-tester.test.ts +174 -0
  92. package/agents/__tests__/66-soc2-control-validator.test.ts +183 -0
  93. package/agents/__tests__/67-wcag-aaa-tester.test.ts +190 -0
  94. package/agents/__tests__/68-dead-code-detector.test.ts +174 -0
  95. package/agents/__tests__/69-type-safety-auditor.test.ts +173 -0
  96. package/agents/__tests__/70-complexity-analyzer.test.ts +177 -0
  97. package/agents/__tests__/registry.test.ts +13 -13
  98. package/agents/registry.ts +146 -5
  99. package/core/__tests__/integration.test.ts +4 -4
  100. package/core/__tests__/orchestrator.test.ts +17 -16
  101. package/core/license.ts +208 -211
  102. package/core/orchestrator.ts +6 -4
  103. package/dist/agents/24-signup-onboarding-tester.d.ts +35 -0
  104. package/dist/agents/24-signup-onboarding-tester.d.ts.map +1 -0
  105. package/dist/agents/24-signup-onboarding-tester.js +357 -0
  106. package/dist/agents/24-signup-onboarding-tester.js.map +1 -0
  107. package/dist/agents/25-crud-flow-tester.d.ts +11 -0
  108. package/dist/agents/25-crud-flow-tester.d.ts.map +1 -0
  109. package/dist/agents/25-crud-flow-tester.js +253 -0
  110. package/dist/agents/25-crud-flow-tester.js.map +1 -0
  111. package/dist/agents/26-form-validator.d.ts +12 -0
  112. package/dist/agents/26-form-validator.d.ts.map +1 -0
  113. package/dist/agents/26-form-validator.js +257 -0
  114. package/dist/agents/26-form-validator.js.map +1 -0
  115. package/dist/agents/27-search-filter-tester.d.ts +20 -0
  116. package/dist/agents/27-search-filter-tester.d.ts.map +1 -0
  117. package/dist/agents/27-search-filter-tester.js +267 -0
  118. package/dist/agents/27-search-filter-tester.js.map +1 -0
  119. package/dist/agents/28-navigation-routing-tester.d.ts +32 -0
  120. package/dist/agents/28-navigation-routing-tester.d.ts.map +1 -0
  121. package/dist/agents/28-navigation-routing-tester.js +363 -0
  122. package/dist/agents/28-navigation-routing-tester.js.map +1 -0
  123. package/dist/agents/29-responsive-interaction-tester.d.ts +26 -0
  124. package/dist/agents/29-responsive-interaction-tester.d.ts.map +1 -0
  125. package/dist/agents/29-responsive-interaction-tester.js +272 -0
  126. package/dist/agents/29-responsive-interaction-tester.js.map +1 -0
  127. package/dist/agents/30-multi-user-scenario-tester.d.ts +24 -0
  128. package/dist/agents/30-multi-user-scenario-tester.d.ts.map +1 -0
  129. package/dist/agents/30-multi-user-scenario-tester.js +254 -0
  130. package/dist/agents/30-multi-user-scenario-tester.js.map +1 -0
  131. package/dist/agents/31-load-tester.d.ts +12 -0
  132. package/dist/agents/31-load-tester.d.ts.map +1 -0
  133. package/dist/agents/31-load-tester.js +110 -0
  134. package/dist/agents/31-load-tester.js.map +1 -0
  135. package/dist/agents/32-memory-leak-detector.d.ts +12 -0
  136. package/dist/agents/32-memory-leak-detector.d.ts.map +1 -0
  137. package/dist/agents/32-memory-leak-detector.js +167 -0
  138. package/dist/agents/32-memory-leak-detector.js.map +1 -0
  139. package/dist/agents/33-bundle-analyzer.d.ts +10 -0
  140. package/dist/agents/33-bundle-analyzer.d.ts.map +1 -0
  141. package/dist/agents/33-bundle-analyzer.js +111 -0
  142. package/dist/agents/33-bundle-analyzer.js.map +1 -0
  143. package/dist/agents/34-xss-scanner.d.ts +11 -0
  144. package/dist/agents/34-xss-scanner.d.ts.map +1 -0
  145. package/dist/agents/34-xss-scanner.js +164 -0
  146. package/dist/agents/34-xss-scanner.js.map +1 -0
  147. package/dist/agents/35-csrf-tester.d.ts +11 -0
  148. package/dist/agents/35-csrf-tester.d.ts.map +1 -0
  149. package/dist/agents/35-csrf-tester.js +70 -0
  150. package/dist/agents/35-csrf-tester.js.map +1 -0
  151. package/dist/agents/36-auth-fuzzer.d.ts +13 -0
  152. package/dist/agents/36-auth-fuzzer.d.ts.map +1 -0
  153. package/dist/agents/36-auth-fuzzer.js +163 -0
  154. package/dist/agents/36-auth-fuzzer.js.map +1 -0
  155. package/dist/agents/37-dependency-scanner.d.ts +11 -0
  156. package/dist/agents/37-dependency-scanner.d.ts.map +1 -0
  157. package/dist/agents/37-dependency-scanner.js +139 -0
  158. package/dist/agents/37-dependency-scanner.js.map +1 -0
  159. package/dist/agents/38-secrets-scanner.d.ts +11 -0
  160. package/dist/agents/38-secrets-scanner.d.ts.map +1 -0
  161. package/dist/agents/38-secrets-scanner.js +116 -0
  162. package/dist/agents/38-secrets-scanner.js.map +1 -0
  163. package/dist/agents/39-api-contract-tester.d.ts +12 -0
  164. package/dist/agents/39-api-contract-tester.d.ts.map +1 -0
  165. package/dist/agents/39-api-contract-tester.js +142 -0
  166. package/dist/agents/39-api-contract-tester.js.map +1 -0
  167. package/dist/agents/40-rate-limit-tester.d.ts +12 -0
  168. package/dist/agents/40-rate-limit-tester.d.ts.map +1 -0
  169. package/dist/agents/40-rate-limit-tester.js +79 -0
  170. package/dist/agents/40-rate-limit-tester.js.map +1 -0
  171. package/dist/agents/41-api-pagination-tester.d.ts +12 -0
  172. package/dist/agents/41-api-pagination-tester.d.ts.map +1 -0
  173. package/dist/agents/41-api-pagination-tester.js +79 -0
  174. package/dist/agents/41-api-pagination-tester.js.map +1 -0
  175. package/dist/agents/42-graphql-tester.d.ts +13 -0
  176. package/dist/agents/42-graphql-tester.d.ts.map +1 -0
  177. package/dist/agents/42-graphql-tester.js +187 -0
  178. package/dist/agents/42-graphql-tester.js.map +1 -0
  179. package/dist/agents/43-data-consistency-checker.d.ts +11 -0
  180. package/dist/agents/43-data-consistency-checker.d.ts.map +1 -0
  181. package/dist/agents/43-data-consistency-checker.js +176 -0
  182. package/dist/agents/43-data-consistency-checker.js.map +1 -0
  183. package/dist/agents/44-backup-recovery-tester.d.ts +11 -0
  184. package/dist/agents/44-backup-recovery-tester.d.ts.map +1 -0
  185. package/dist/agents/44-backup-recovery-tester.js +128 -0
  186. package/dist/agents/44-backup-recovery-tester.js.map +1 -0
  187. package/dist/agents/45-data-privacy-scanner.d.ts +11 -0
  188. package/dist/agents/45-data-privacy-scanner.d.ts.map +1 -0
  189. package/dist/agents/45-data-privacy-scanner.js +100 -0
  190. package/dist/agents/45-data-privacy-scanner.js.map +1 -0
  191. package/dist/agents/46-seo-auditor.d.ts +12 -0
  192. package/dist/agents/46-seo-auditor.d.ts.map +1 -0
  193. package/dist/agents/46-seo-auditor.js +275 -0
  194. package/dist/agents/46-seo-auditor.js.map +1 -0
  195. package/dist/agents/47-social-preview-tester.d.ts +11 -0
  196. package/dist/agents/47-social-preview-tester.d.ts.map +1 -0
  197. package/dist/agents/47-social-preview-tester.js +219 -0
  198. package/dist/agents/47-social-preview-tester.js.map +1 -0
  199. package/dist/agents/48-lighthouse-auditor.d.ts +11 -0
  200. package/dist/agents/48-lighthouse-auditor.d.ts.map +1 -0
  201. package/dist/agents/48-lighthouse-auditor.js +192 -0
  202. package/dist/agents/48-lighthouse-auditor.js.map +1 -0
  203. package/dist/agents/49-i18n-tester.d.ts +13 -0
  204. package/dist/agents/49-i18n-tester.d.ts.map +1 -0
  205. package/dist/agents/49-i18n-tester.js +172 -0
  206. package/dist/agents/49-i18n-tester.js.map +1 -0
  207. package/dist/agents/50-timezone-tester.d.ts +11 -0
  208. package/dist/agents/50-timezone-tester.d.ts.map +1 -0
  209. package/dist/agents/50-timezone-tester.js +152 -0
  210. package/dist/agents/50-timezone-tester.js.map +1 -0
  211. package/dist/agents/51-error-recovery-tester.d.ts +11 -0
  212. package/dist/agents/51-error-recovery-tester.d.ts.map +1 -0
  213. package/dist/agents/51-error-recovery-tester.js +134 -0
  214. package/dist/agents/51-error-recovery-tester.js.map +1 -0
  215. package/dist/agents/52-offline-mode-tester.d.ts +12 -0
  216. package/dist/agents/52-offline-mode-tester.d.ts.map +1 -0
  217. package/dist/agents/52-offline-mode-tester.js +161 -0
  218. package/dist/agents/52-offline-mode-tester.js.map +1 -0
  219. package/dist/agents/53-graceful-degradation-tester.d.ts +12 -0
  220. package/dist/agents/53-graceful-degradation-tester.d.ts.map +1 -0
  221. package/dist/agents/53-graceful-degradation-tester.js +130 -0
  222. package/dist/agents/53-graceful-degradation-tester.js.map +1 -0
  223. package/dist/agents/54-websocket-tester.d.ts +10 -0
  224. package/dist/agents/54-websocket-tester.d.ts.map +1 -0
  225. package/dist/agents/54-websocket-tester.js +132 -0
  226. package/dist/agents/54-websocket-tester.js.map +1 -0
  227. package/dist/agents/55-realtime-sync-tester.d.ts +11 -0
  228. package/dist/agents/55-realtime-sync-tester.d.ts.map +1 -0
  229. package/dist/agents/55-realtime-sync-tester.js +175 -0
  230. package/dist/agents/55-realtime-sync-tester.js.map +1 -0
  231. package/dist/agents/56-file-upload-tester.d.ts +12 -0
  232. package/dist/agents/56-file-upload-tester.d.ts.map +1 -0
  233. package/dist/agents/56-file-upload-tester.js +166 -0
  234. package/dist/agents/56-file-upload-tester.js.map +1 -0
  235. package/dist/agents/57-export-tester.d.ts +11 -0
  236. package/dist/agents/57-export-tester.d.ts.map +1 -0
  237. package/dist/agents/57-export-tester.js +155 -0
  238. package/dist/agents/57-export-tester.js.map +1 -0
  239. package/dist/agents/58-payment-flow-tester.d.ts +11 -0
  240. package/dist/agents/58-payment-flow-tester.d.ts.map +1 -0
  241. package/dist/agents/58-payment-flow-tester.js +159 -0
  242. package/dist/agents/58-payment-flow-tester.js.map +1 -0
  243. package/dist/agents/59-ssl-tls-auditor.d.ts +10 -0
  244. package/dist/agents/59-ssl-tls-auditor.d.ts.map +1 -0
  245. package/dist/agents/59-ssl-tls-auditor.js +132 -0
  246. package/dist/agents/59-ssl-tls-auditor.js.map +1 -0
  247. package/dist/agents/60-dns-cdn-tester.d.ts +10 -0
  248. package/dist/agents/60-dns-cdn-tester.d.ts.map +1 -0
  249. package/dist/agents/60-dns-cdn-tester.js +105 -0
  250. package/dist/agents/60-dns-cdn-tester.js.map +1 -0
  251. package/dist/agents/61-docker-health-checker.d.ts +10 -0
  252. package/dist/agents/61-docker-health-checker.d.ts.map +1 -0
  253. package/dist/agents/61-docker-health-checker.js +95 -0
  254. package/dist/agents/61-docker-health-checker.js.map +1 -0
  255. package/dist/agents/62-env-config-validator.d.ts +10 -0
  256. package/dist/agents/62-env-config-validator.d.ts.map +1 -0
  257. package/dist/agents/62-env-config-validator.js +132 -0
  258. package/dist/agents/62-env-config-validator.js.map +1 -0
  259. package/dist/agents/63-log-quality-auditor.d.ts +9 -0
  260. package/dist/agents/63-log-quality-auditor.d.ts.map +1 -0
  261. package/dist/agents/63-log-quality-auditor.js +121 -0
  262. package/dist/agents/63-log-quality-auditor.js.map +1 -0
  263. package/dist/agents/64-analytics-tracker-tester.d.ts +10 -0
  264. package/dist/agents/64-analytics-tracker-tester.d.ts.map +1 -0
  265. package/dist/agents/64-analytics-tracker-tester.js +146 -0
  266. package/dist/agents/64-analytics-tracker-tester.js.map +1 -0
  267. package/dist/agents/65-gdpr-compliance-tester.d.ts +13 -0
  268. package/dist/agents/65-gdpr-compliance-tester.d.ts.map +1 -0
  269. package/dist/agents/65-gdpr-compliance-tester.js +186 -0
  270. package/dist/agents/65-gdpr-compliance-tester.js.map +1 -0
  271. package/dist/agents/66-soc2-control-validator.d.ts +14 -0
  272. package/dist/agents/66-soc2-control-validator.d.ts.map +1 -0
  273. package/dist/agents/66-soc2-control-validator.js +178 -0
  274. package/dist/agents/66-soc2-control-validator.js.map +1 -0
  275. package/dist/agents/67-wcag-aaa-tester.d.ts +13 -0
  276. package/dist/agents/67-wcag-aaa-tester.d.ts.map +1 -0
  277. package/dist/agents/67-wcag-aaa-tester.js +207 -0
  278. package/dist/agents/67-wcag-aaa-tester.js.map +1 -0
  279. package/dist/agents/68-dead-code-detector.d.ts +9 -0
  280. package/dist/agents/68-dead-code-detector.d.ts.map +1 -0
  281. package/dist/agents/68-dead-code-detector.js +116 -0
  282. package/dist/agents/68-dead-code-detector.js.map +1 -0
  283. package/dist/agents/69-type-safety-auditor.d.ts +9 -0
  284. package/dist/agents/69-type-safety-auditor.d.ts.map +1 -0
  285. package/dist/agents/69-type-safety-auditor.js +148 -0
  286. package/dist/agents/69-type-safety-auditor.js.map +1 -0
  287. package/dist/agents/70-complexity-analyzer.d.ts +10 -0
  288. package/dist/agents/70-complexity-analyzer.d.ts.map +1 -0
  289. package/dist/agents/70-complexity-analyzer.js +154 -0
  290. package/dist/agents/70-complexity-analyzer.js.map +1 -0
  291. package/dist/agents/registry.d.ts +3 -3
  292. package/dist/agents/registry.d.ts.map +1 -1
  293. package/dist/agents/registry.js +146 -5
  294. package/dist/agents/registry.js.map +1 -1
  295. package/dist/core/license.js +2 -5
  296. package/dist/core/license.js.map +1 -1
  297. package/dist/core/orchestrator.d.ts.map +1 -1
  298. package/dist/core/orchestrator.js +6 -4
  299. package/dist/core/orchestrator.js.map +1 -1
  300. package/package.json +2 -2
@@ -0,0 +1,322 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Stub config helpers
7
+ // ---------------------------------------------------------------------------
8
+
9
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
10
+ return {
11
+ schemaVersion: 1,
12
+ name: 'test',
13
+ auth: {
14
+ loginUrl: 'http://localhost/login',
15
+ credentials: {
16
+ admin: { email: 'admin@test.com', password: 'secret' },
17
+ },
18
+ },
19
+ modules: [
20
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
21
+ { id: 'tasks', route: '/tasks', sidebarIcon: true },
22
+ ],
23
+ environments: {
24
+ alpha: { baseUrl: 'http://localhost:3000', seed: false },
25
+ },
26
+ portals: [],
27
+ breakpoints: [],
28
+ integrations: [],
29
+ workers: [],
30
+ workflows: [],
31
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
32
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
33
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
34
+ agentClassification: { blocking: [], advisory: [25] },
35
+ thresholds: {
36
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
37
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
38
+ apiResponseTime: 2000,
39
+ bundleSizeLimit: 500000,
40
+ maxConsoleErrors: 0,
41
+ },
42
+ disabledAgents: [],
43
+ disabledAgentSet: new Set<number>(),
44
+ ...overrides,
45
+ };
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Mock fetch (preFlight URL reachability)
50
+ // ---------------------------------------------------------------------------
51
+
52
+ const fetchMock = vi.fn();
53
+ vi.stubGlobal('fetch', fetchMock);
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Mock helpers
57
+ // ---------------------------------------------------------------------------
58
+
59
+ const loginMock = vi.fn().mockResolvedValue(undefined);
60
+
61
+ vi.mock('../../helpers/navigation', () => ({
62
+ login: (...args: unknown[]) => loginMock(...args),
63
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
64
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
65
+ switchPortal: vi.fn().mockResolvedValue(undefined),
66
+ }));
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Mock Playwright
70
+ // ---------------------------------------------------------------------------
71
+
72
+ interface MockLocator {
73
+ count: ReturnType<typeof vi.fn>;
74
+ first: ReturnType<typeof vi.fn>;
75
+ nth: ReturnType<typeof vi.fn>;
76
+ click: ReturnType<typeof vi.fn>;
77
+ fill: ReturnType<typeof vi.fn>;
78
+ }
79
+
80
+ function makeMockLocator(countVal = 0): MockLocator {
81
+ const locator: MockLocator = {
82
+ count: vi.fn().mockResolvedValue(countVal),
83
+ first: vi.fn(),
84
+ nth: vi.fn(),
85
+ click: vi.fn().mockResolvedValue(undefined),
86
+ fill: vi.fn().mockResolvedValue(undefined),
87
+ };
88
+ locator.first.mockReturnValue(locator);
89
+ locator.nth.mockReturnValue(locator);
90
+ return locator;
91
+ }
92
+
93
+ interface MockPage {
94
+ close: ReturnType<typeof vi.fn>;
95
+ goto: ReturnType<typeof vi.fn>;
96
+ click: ReturnType<typeof vi.fn>;
97
+ locator: ReturnType<typeof vi.fn>;
98
+ waitForSelector: ReturnType<typeof vi.fn>;
99
+ }
100
+
101
+ function makeMockPage(): MockPage {
102
+ const defaultLocator = makeMockLocator(0);
103
+ return {
104
+ close: vi.fn().mockResolvedValue(undefined),
105
+ goto: vi.fn().mockResolvedValue(undefined),
106
+ click: vi.fn().mockResolvedValue(undefined),
107
+ locator: vi.fn().mockReturnValue(defaultLocator),
108
+ waitForSelector: vi.fn().mockResolvedValue({ textContent: vi.fn().mockResolvedValue('') }),
109
+ };
110
+ }
111
+
112
+ let currentMockPage: MockPage;
113
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
114
+
115
+ vi.mock('playwright', async () => ({
116
+ chromium: {
117
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
118
+ },
119
+ }));
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Dynamic import AFTER mocks are hoisted
123
+ // ---------------------------------------------------------------------------
124
+
125
+ const { CrudFlowTesterAgent } = await import('../25-crud-flow-tester');
126
+
127
+ function createAgent(
128
+ config: ValidatedConfig = makeConfig(),
129
+ phase: Phase = 'beta',
130
+ ): InstanceType<typeof CrudFlowTesterAgent> {
131
+ return new CrudFlowTesterAgent(config, phase, '/tmp/test-run');
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Tests
136
+ // ---------------------------------------------------------------------------
137
+
138
+ describe('CrudFlowTesterAgent', () => {
139
+ beforeEach(() => {
140
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
141
+ loginMock.mockReset().mockResolvedValue(undefined);
142
+
143
+ currentMockPage = makeMockPage();
144
+ mockBrowser = {
145
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
146
+ close: vi.fn().mockResolvedValue(undefined),
147
+ };
148
+ });
149
+
150
+ // Identity
151
+ it('has agentId 25 and correct agentName', () => {
152
+ const agent = createAgent();
153
+ expect(agent.agentId).toBe(25);
154
+ expect(agent.agentName).toBe('CRUD Flow Tester');
155
+ });
156
+
157
+ // preFlight
158
+ it('throws when modules array is empty', async () => {
159
+ const config = makeConfig({ modules: [] });
160
+ const agent = createAgent(config);
161
+ const result = await agent.run();
162
+
163
+ expect(result.status).toBe('failed');
164
+ expect(result.findings[0].description).toContain('module');
165
+ });
166
+
167
+ it('resolves environment in preFlight', async () => {
168
+ const agent = createAgent();
169
+ const result = await agent.run();
170
+ // Should not have a preFlight error
171
+ expect(result.findings.every(f => !f.description.includes('preFlight'))).toBe(true);
172
+ });
173
+
174
+ // No CRUD buttons found
175
+ it('produces low finding when no CRUD actions found for a module', async () => {
176
+ // All locator().count() returns 0 by default
177
+ const agent = createAgent();
178
+ const result = await agent.run();
179
+
180
+ const noCrudFindings = result.findings.filter(f => f.description.includes('No CRUD actions'));
181
+ expect(noCrudFindings.length).toBeGreaterThan(0);
182
+ expect(noCrudFindings[0].severity).toBe('low');
183
+ });
184
+
185
+ // CRUD buttons found — create flow
186
+ it('clicks create button when found and tests the flow', async () => {
187
+ const createLocator = makeMockLocator(1);
188
+ const formLocator = makeMockLocator(1);
189
+ const inputLocator = makeMockLocator(1);
190
+ const submitLocator = makeMockLocator(1);
191
+ const editLocator = makeMockLocator(0);
192
+ const deleteLocator = makeMockLocator(0);
193
+
194
+ inputLocator.fill = vi.fn().mockResolvedValue(undefined);
195
+ inputLocator.nth = vi.fn().mockReturnValue(inputLocator);
196
+
197
+ currentMockPage.locator.mockImplementation((sel: string) => {
198
+ if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
199
+ if (sel.includes('form') || sel.includes('dialog') || sel.includes('modal')) return formLocator;
200
+ if (sel.includes('input[type="text"]')) return inputLocator;
201
+ if (sel.includes('submit') || sel.includes('Save')) return submitLocator;
202
+ if (sel.includes('Edit')) return editLocator;
203
+ if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
204
+ return makeMockLocator(0);
205
+ });
206
+
207
+ currentMockPage.waitForSelector.mockResolvedValue({});
208
+
209
+ const agent = createAgent();
210
+ const result = await agent.run();
211
+
212
+ expect(createLocator.first).toHaveBeenCalled();
213
+ });
214
+
215
+ // Create fails
216
+ it('produces medium finding when create action fails', async () => {
217
+ const createLocator = makeMockLocator(1);
218
+ createLocator.first.mockReturnValue({
219
+ click: vi.fn().mockRejectedValue(new Error('Click failed')),
220
+ });
221
+
222
+ const editLocator = makeMockLocator(0);
223
+ const deleteLocator = makeMockLocator(0);
224
+
225
+ currentMockPage.locator.mockImplementation((sel: string) => {
226
+ if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
227
+ if (sel.includes('Edit')) return editLocator;
228
+ if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
229
+ return makeMockLocator(0);
230
+ });
231
+
232
+ const agent = createAgent();
233
+ const result = await agent.run();
234
+
235
+ const createFailed = result.findings.filter(f => f.description.includes('Create action failed'));
236
+ expect(createFailed.length).toBeGreaterThan(0);
237
+ expect(createFailed[0].severity).toBe('medium');
238
+ });
239
+
240
+ // Delete fails
241
+ it('produces medium finding when delete action fails', async () => {
242
+ const createLocator = makeMockLocator(0);
243
+ const editLocator = makeMockLocator(0);
244
+ const deleteLocator = makeMockLocator(1);
245
+ deleteLocator.first.mockReturnValue({
246
+ click: vi.fn().mockRejectedValue(new Error('Delete error')),
247
+ });
248
+
249
+ currentMockPage.locator.mockImplementation((sel: string) => {
250
+ if (sel.includes('Create') || sel.includes('Add') || sel.includes('New')) return createLocator;
251
+ if (sel.includes('Edit')) return editLocator;
252
+ if (sel.includes('Delete') || sel.includes('Remove')) return deleteLocator;
253
+ return makeMockLocator(0);
254
+ });
255
+
256
+ const agent = createAgent();
257
+ const result = await agent.run();
258
+
259
+ const deleteFailed = result.findings.filter(f => f.description.includes('Delete action failed'));
260
+ expect(deleteFailed.length).toBeGreaterThan(0);
261
+ expect(deleteFailed[0].severity).toBe('medium');
262
+ });
263
+
264
+ // Login failure — graceful degradation
265
+ it('produces medium finding and continues when login fails', async () => {
266
+ loginMock.mockRejectedValue(new Error('Invalid credentials'));
267
+
268
+ const agent = createAgent();
269
+ const result = await agent.run();
270
+
271
+ const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
272
+ expect(loginFinding).toBeDefined();
273
+ expect(loginFinding!.severity).toBe('medium');
274
+ });
275
+
276
+ // No credentials — graceful
277
+ it('produces medium finding when no credentials configured', async () => {
278
+ const config = makeConfig({
279
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
280
+ });
281
+ const agent = createAgent(config);
282
+ const result = await agent.run();
283
+
284
+ const noCredsFinding = result.findings.find(f => f.id.includes('no-credentials'));
285
+ expect(noCredsFinding).toBeDefined();
286
+ expect(noCredsFinding!.severity).toBe('medium');
287
+ });
288
+
289
+ // Playwright crash — graceful
290
+ it('produces medium finding when Playwright crashes', async () => {
291
+ mockBrowser.newPage.mockRejectedValue(new Error('Browser crashed'));
292
+
293
+ const agent = createAgent();
294
+ const result = await agent.run();
295
+
296
+ const pwFinding = result.findings.find(f => f.id.includes('playwright-failure'));
297
+ expect(pwFinding).toBeDefined();
298
+ expect(pwFinding!.severity).toBe('medium');
299
+ });
300
+
301
+ // Navigation failure for a module
302
+ it('produces medium finding when module navigation fails', async () => {
303
+ currentMockPage.goto.mockRejectedValue(new Error('Navigation failed'));
304
+
305
+ const agent = createAgent();
306
+ const result = await agent.run();
307
+
308
+ const navFinding = result.findings.find(f => f.id.includes('nav-failed'));
309
+ expect(navFinding).toBeDefined();
310
+ expect(navFinding!.severity).toBe('medium');
311
+ });
312
+
313
+ // Browser is closed in finally block
314
+ it('closes browser even when an error occurs', async () => {
315
+ mockBrowser.newPage.mockRejectedValue(new Error('Launch error'));
316
+
317
+ const agent = createAgent();
318
+ await agent.run();
319
+
320
+ expect(mockBrowser.close).toHaveBeenCalled();
321
+ });
322
+ });
@@ -0,0 +1,345 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Stub config helpers
7
+ // ---------------------------------------------------------------------------
8
+
9
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
10
+ return {
11
+ schemaVersion: 1,
12
+ name: 'test',
13
+ auth: {
14
+ loginUrl: 'http://localhost/login',
15
+ credentials: {
16
+ admin: { email: 'admin@test.com', password: 'secret' },
17
+ },
18
+ },
19
+ modules: [
20
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
21
+ ],
22
+ environments: {
23
+ alpha: { baseUrl: 'http://localhost:3000', seed: false },
24
+ },
25
+ portals: [],
26
+ breakpoints: [],
27
+ integrations: [],
28
+ workers: [],
29
+ workflows: [],
30
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
31
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
32
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
33
+ agentClassification: { blocking: [], advisory: [26] },
34
+ thresholds: {
35
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
36
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
37
+ apiResponseTime: 2000,
38
+ bundleSizeLimit: 500000,
39
+ maxConsoleErrors: 0,
40
+ },
41
+ disabledAgents: [],
42
+ disabledAgentSet: new Set<number>(),
43
+ ...overrides,
44
+ };
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Mock fetch (preFlight URL reachability)
49
+ // ---------------------------------------------------------------------------
50
+
51
+ const fetchMock = vi.fn();
52
+ vi.stubGlobal('fetch', fetchMock);
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Mock helpers
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const loginMock = vi.fn().mockResolvedValue(undefined);
59
+
60
+ vi.mock('../../helpers/navigation', () => ({
61
+ login: (...args: unknown[]) => loginMock(...args),
62
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
63
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
64
+ switchPortal: vi.fn().mockResolvedValue(undefined),
65
+ }));
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Mock Playwright
69
+ // ---------------------------------------------------------------------------
70
+
71
+ interface MockLocator {
72
+ count: ReturnType<typeof vi.fn>;
73
+ first: ReturnType<typeof vi.fn>;
74
+ nth: ReturnType<typeof vi.fn>;
75
+ click: ReturnType<typeof vi.fn>;
76
+ fill: ReturnType<typeof vi.fn>;
77
+ check: ReturnType<typeof vi.fn>;
78
+ selectOption: ReturnType<typeof vi.fn>;
79
+ locator: ReturnType<typeof vi.fn>;
80
+ evaluate: ReturnType<typeof vi.fn>;
81
+ getAttribute: ReturnType<typeof vi.fn>;
82
+ }
83
+
84
+ function makeMockLocator(countVal = 0): MockLocator {
85
+ const locator: MockLocator = {
86
+ count: vi.fn().mockResolvedValue(countVal),
87
+ first: vi.fn(),
88
+ nth: vi.fn(),
89
+ click: vi.fn().mockResolvedValue(undefined),
90
+ fill: vi.fn().mockResolvedValue(undefined),
91
+ check: vi.fn().mockResolvedValue(undefined),
92
+ selectOption: vi.fn().mockResolvedValue(undefined),
93
+ locator: vi.fn().mockReturnValue({
94
+ count: vi.fn().mockResolvedValue(0),
95
+ nth: vi.fn().mockReturnValue({
96
+ getAttribute: vi.fn().mockResolvedValue(null),
97
+ }),
98
+ }),
99
+ evaluate: vi.fn().mockResolvedValue('input'),
100
+ getAttribute: vi.fn().mockResolvedValue('text'),
101
+ };
102
+ locator.first.mockReturnValue(locator);
103
+ locator.nth.mockReturnValue(locator);
104
+ return locator;
105
+ }
106
+
107
+ interface MockPage {
108
+ close: ReturnType<typeof vi.fn>;
109
+ goto: ReturnType<typeof vi.fn>;
110
+ locator: ReturnType<typeof vi.fn>;
111
+ waitForSelector: ReturnType<typeof vi.fn>;
112
+ waitForTimeout: ReturnType<typeof vi.fn>;
113
+ }
114
+
115
+ function makeMockPage(): MockPage {
116
+ const defaultLocator = makeMockLocator(0);
117
+ return {
118
+ close: vi.fn().mockResolvedValue(undefined),
119
+ goto: vi.fn().mockResolvedValue(undefined),
120
+ locator: vi.fn().mockReturnValue(defaultLocator),
121
+ waitForSelector: vi.fn().mockResolvedValue({ textContent: vi.fn().mockResolvedValue('') }),
122
+ waitForTimeout: vi.fn().mockResolvedValue(undefined),
123
+ };
124
+ }
125
+
126
+ let currentMockPage: MockPage;
127
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
128
+
129
+ vi.mock('playwright', async () => ({
130
+ chromium: {
131
+ launch: vi.fn().mockImplementation(async () => mockBrowser),
132
+ },
133
+ }));
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Dynamic import AFTER mocks are hoisted
137
+ // ---------------------------------------------------------------------------
138
+
139
+ const { FormValidatorAgent } = await import('../26-form-validator');
140
+
141
+ function createAgent(
142
+ config: ValidatedConfig = makeConfig(),
143
+ phase: Phase = 'beta',
144
+ ): InstanceType<typeof FormValidatorAgent> {
145
+ return new FormValidatorAgent(config, phase, '/tmp/test-run');
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Tests
150
+ // ---------------------------------------------------------------------------
151
+
152
+ describe('FormValidatorAgent', () => {
153
+ beforeEach(() => {
154
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
155
+ loginMock.mockReset().mockResolvedValue(undefined);
156
+
157
+ currentMockPage = makeMockPage();
158
+ mockBrowser = {
159
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
160
+ close: vi.fn().mockResolvedValue(undefined),
161
+ };
162
+ });
163
+
164
+ // Identity
165
+ it('has agentId 26 and correct agentName', () => {
166
+ const agent = createAgent();
167
+ expect(agent.agentId).toBe(26);
168
+ expect(agent.agentName).toBe('Form Validator');
169
+ });
170
+
171
+ // preFlight
172
+ it('throws when modules array is empty', async () => {
173
+ const config = makeConfig({ modules: [] });
174
+ const agent = createAgent(config);
175
+ const result = await agent.run();
176
+
177
+ expect(result.status).toBe('failed');
178
+ expect(result.findings[0].description).toContain('module');
179
+ });
180
+
181
+ // No forms found
182
+ it('produces low finding when no forms found in a module', async () => {
183
+ // form locator returns count 0
184
+ const formLocator = makeMockLocator(0);
185
+ currentMockPage.locator.mockImplementation((sel: string) => {
186
+ if (sel === 'form') return formLocator;
187
+ return makeMockLocator(0);
188
+ });
189
+
190
+ const agent = createAgent();
191
+ const result = await agent.run();
192
+
193
+ const noFormFindings = result.findings.filter(f => f.description.includes('No forms found'));
194
+ expect(noFormFindings.length).toBeGreaterThan(0);
195
+ expect(noFormFindings[0].severity).toBe('low');
196
+ });
197
+
198
+ // Form accepts empty submission (high finding)
199
+ it('produces high finding when form accepts empty submission without validation', async () => {
200
+ const submitLocator = makeMockLocator(1);
201
+ const inputLocator = makeMockLocator(2);
202
+ const errorLocator = makeMockLocator(0); // No validation errors
203
+
204
+ const formLocator = makeMockLocator(1);
205
+ formLocator.nth = vi.fn().mockReturnValue(formLocator);
206
+ formLocator.locator.mockImplementation((sel: string) => {
207
+ if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
208
+ return inputLocator;
209
+ });
210
+
211
+ currentMockPage.locator.mockImplementation((sel: string) => {
212
+ if (sel === 'form') return formLocator;
213
+ // error selectors checked via page.locator(errorSelector)
214
+ if (sel.includes('error') || sel.includes('invalid') || sel.includes('red') || sel.includes('alert')) return errorLocator;
215
+ return makeMockLocator(0);
216
+ });
217
+
218
+ const agent = createAgent();
219
+ const result = await agent.run();
220
+
221
+ const emptySubmitFinding = result.findings.filter(f =>
222
+ f.description.includes('accepts empty submission'),
223
+ );
224
+ expect(emptySubmitFinding.length).toBeGreaterThan(0);
225
+ expect(emptySubmitFinding[0].severity).toBe('high');
226
+ });
227
+
228
+ // Form shows validation errors on empty submit
229
+ it('produces no finding when form shows validation errors on empty submit', async () => {
230
+ const submitLocator = makeMockLocator(1);
231
+ const inputLocator = makeMockLocator(2);
232
+ const errorLocator = makeMockLocator(3); // Validation errors present
233
+
234
+ const formLocator = makeMockLocator(1);
235
+ formLocator.nth = vi.fn().mockReturnValue(formLocator);
236
+ formLocator.locator.mockImplementation((sel: string) => {
237
+ if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
238
+ return inputLocator;
239
+ });
240
+
241
+ currentMockPage.locator.mockImplementation((sel: string) => {
242
+ if (sel === 'form') return formLocator;
243
+ // Error selectors checked via page.locator
244
+ if (sel.includes('error') || sel.includes('invalid') || sel.includes('red') || sel.includes('alert') || sel.includes('field-error') || sel.includes('validation')) return errorLocator;
245
+ return makeMockLocator(0);
246
+ });
247
+
248
+ const agent = createAgent();
249
+ const result = await agent.run();
250
+
251
+ const emptySubmitFinding = result.findings.filter(f =>
252
+ f.description.includes('accepts empty submission'),
253
+ );
254
+ expect(emptySubmitFinding.length).toBe(0);
255
+ });
256
+
257
+ // Login failure — graceful degradation
258
+ it('produces medium finding and continues when login fails', async () => {
259
+ loginMock.mockRejectedValue(new Error('Invalid credentials'));
260
+
261
+ const agent = createAgent();
262
+ const result = await agent.run();
263
+
264
+ const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
265
+ expect(loginFinding).toBeDefined();
266
+ expect(loginFinding!.severity).toBe('medium');
267
+ });
268
+
269
+ // No credentials
270
+ it('produces medium finding when no credentials configured', async () => {
271
+ const config = makeConfig({
272
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
273
+ });
274
+ const agent = createAgent(config);
275
+ const result = await agent.run();
276
+
277
+ const noCredsFinding = result.findings.find(f => f.id.includes('no-credentials'));
278
+ expect(noCredsFinding).toBeDefined();
279
+ expect(noCredsFinding!.severity).toBe('medium');
280
+ });
281
+
282
+ // Playwright crash
283
+ it('produces medium finding when Playwright crashes', async () => {
284
+ mockBrowser.newPage.mockRejectedValue(new Error('Browser crashed'));
285
+
286
+ const agent = createAgent();
287
+ const result = await agent.run();
288
+
289
+ const pwFinding = result.findings.find(f => f.id.includes('playwright-failure'));
290
+ expect(pwFinding).toBeDefined();
291
+ expect(pwFinding!.severity).toBe('medium');
292
+ });
293
+
294
+ // Module navigation failure
295
+ it('produces medium finding when module navigation fails', async () => {
296
+ currentMockPage.goto.mockRejectedValue(new Error('Navigation failed'));
297
+
298
+ const agent = createAgent();
299
+ const result = await agent.run();
300
+
301
+ const navFinding = result.findings.find(f => f.id.includes('nav-failed'));
302
+ expect(navFinding).toBeDefined();
303
+ expect(navFinding!.severity).toBe('medium');
304
+ });
305
+
306
+ // Browser closed in finally
307
+ it('closes browser even when an error occurs', async () => {
308
+ mockBrowser.newPage.mockRejectedValue(new Error('Launch error'));
309
+
310
+ const agent = createAgent();
311
+ await agent.run();
312
+
313
+ expect(mockBrowser.close).toHaveBeenCalled();
314
+ });
315
+
316
+ // Submit button click failure
317
+ it('produces medium finding when submit button click throws', async () => {
318
+ const submitLocator = makeMockLocator(1);
319
+ submitLocator.first.mockReturnValue({
320
+ click: vi.fn().mockRejectedValue(new Error('Submit click failed')),
321
+ });
322
+
323
+ const inputLocator = makeMockLocator(2);
324
+
325
+ const formLocator = makeMockLocator(1);
326
+ formLocator.nth = vi.fn().mockReturnValue(formLocator);
327
+ // form.locator() returns inputs or submit button depending on selector
328
+ formLocator.locator.mockImplementation((sel: string) => {
329
+ if (sel.includes('submit') || sel.includes('Submit') || sel.includes('Save')) return submitLocator;
330
+ return inputLocator;
331
+ });
332
+
333
+ currentMockPage.locator.mockImplementation((sel: string) => {
334
+ if (sel === 'form') return formLocator;
335
+ return makeMockLocator(0);
336
+ });
337
+
338
+ const agent = createAgent();
339
+ const result = await agent.run();
340
+
341
+ const submitError = result.findings.filter(f => f.description.includes('submit test failed'));
342
+ expect(submitError.length).toBeGreaterThan(0);
343
+ expect(submitError[0].severity).toBe('medium');
344
+ });
345
+ });