@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,302 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Finding } from '../core/types';
3
+ import { BaseAgent } from './base-agent';
4
+ import { resolveEnvironment } from '../helpers/env-resolver';
5
+ import { login } from '../helpers/navigation';
6
+
7
+ /** Timeout (ms) for individual module CRUD operations. */
8
+ const OP_TIMEOUT_MS = 5_000;
9
+
10
+ /** Selectors that indicate common CRUD action buttons. */
11
+ const CREATE_SELECTORS = [
12
+ 'button:has-text("Create")',
13
+ 'button:has-text("Add")',
14
+ 'button:has-text("New")',
15
+ '[data-testid*="create"]',
16
+ '[data-testid*="add"]',
17
+ '[data-testid*="new"]',
18
+ ] as const;
19
+
20
+ const EDIT_SELECTORS = [
21
+ 'button:has-text("Edit")',
22
+ '[data-testid*="edit"]',
23
+ 'a:has-text("Edit")',
24
+ ] as const;
25
+
26
+ const DELETE_SELECTORS = [
27
+ 'button:has-text("Delete")',
28
+ '[data-testid*="delete"]',
29
+ 'button:has-text("Remove")',
30
+ ] as const;
31
+
32
+ const SUCCESS_SELECTORS = [
33
+ '[class*="toast"]',
34
+ '[role="alert"]',
35
+ '[data-testid*="success"]',
36
+ '.success',
37
+ '.notification',
38
+ ] as const;
39
+
40
+ export class CrudFlowTesterAgent extends BaseAgent {
41
+ readonly agentId = 25;
42
+ readonly agentName = 'CRUD Flow Tester';
43
+ private baseUrl = '';
44
+
45
+ protected async preFlight(): Promise<void> {
46
+ if (!this.config.modules || this.config.modules.length === 0) {
47
+ throw new Error('CrudFlowTesterAgent requires at least one module in config.modules');
48
+ }
49
+
50
+ const { env } = await resolveEnvironment(this.config, this.phase);
51
+ this.baseUrl = env.baseUrl;
52
+ }
53
+
54
+ protected async execute(): Promise<Finding[]> {
55
+ const findings: Finding[] = [];
56
+
57
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
58
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
59
+ const credentials =
60
+ this.config.auth.credentials?.['admin'] ??
61
+ Object.values(this.config.auth.credentials ?? {})[0];
62
+
63
+ if (!credentials) {
64
+ findings.push({
65
+ id: `${this.agentId}-no-credentials`,
66
+ type: 'infra-issue',
67
+ severity: 'medium',
68
+ agentId: this.agentId,
69
+ module: 'crud-flow-tester',
70
+ description: 'No credentials configured — continuing without authentication',
71
+ });
72
+ }
73
+
74
+ const { chromium } = await import('playwright');
75
+
76
+ let browser: Browser | null = null;
77
+
78
+ try {
79
+ browser = await chromium.launch({ headless: true });
80
+ const page = await browser.newPage();
81
+
82
+ if (credentials) {
83
+ try {
84
+ await login(page, credentials, loginUrl);
85
+ } catch (loginError) {
86
+ findings.push({
87
+ id: `${this.agentId}-login-failed`,
88
+ type: 'infra-issue',
89
+ severity: 'medium',
90
+ agentId: this.agentId,
91
+ module: 'crud-flow-tester',
92
+ description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
93
+ });
94
+ }
95
+ }
96
+
97
+ for (const mod of this.config.modules) {
98
+ const moduleFindings = await this.testModuleCrud(page, mod.id, mod.route);
99
+ findings.push(...moduleFindings);
100
+ }
101
+
102
+ await page.close().catch(() => undefined);
103
+ } catch (playwrightError) {
104
+ findings.push({
105
+ id: `${this.agentId}-playwright-failure`,
106
+ type: 'infra-issue',
107
+ severity: 'medium',
108
+ agentId: this.agentId,
109
+ module: 'crud-flow-tester',
110
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
111
+ });
112
+ } finally {
113
+ if (browser) await browser.close().catch(() => undefined);
114
+ }
115
+
116
+ return findings;
117
+ }
118
+
119
+ private async testModuleCrud(
120
+ page: Page,
121
+ moduleId: string,
122
+ moduleRoute: string,
123
+ ): Promise<Finding[]> {
124
+ const findings: Finding[] = [];
125
+ const fullUrl = moduleRoute.startsWith('http')
126
+ ? moduleRoute
127
+ : `${this.baseUrl}${moduleRoute}`;
128
+
129
+ try {
130
+ await Promise.race([
131
+ page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
132
+ new Promise((_, reject) =>
133
+ setTimeout(() => reject(new Error('Navigation timeout')), OP_TIMEOUT_MS),
134
+ ),
135
+ ]);
136
+ } catch {
137
+ findings.push({
138
+ id: `${this.agentId}-nav-failed-${moduleId}`,
139
+ type: 'test-bug',
140
+ severity: 'medium',
141
+ agentId: this.agentId,
142
+ module: moduleId,
143
+ description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
144
+ });
145
+ return findings;
146
+ }
147
+
148
+ // Discover create buttons
149
+ const createSelector = CREATE_SELECTORS.join(', ');
150
+ let hasCreateButton = false;
151
+
152
+ try {
153
+ const createCount = await Promise.race([
154
+ page.locator(createSelector).count(),
155
+ new Promise<number>((_, reject) =>
156
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
157
+ ),
158
+ ]) as number;
159
+
160
+ hasCreateButton = createCount > 0;
161
+ } catch {
162
+ // Timeout or error discovering create buttons
163
+ }
164
+
165
+ if (!hasCreateButton) {
166
+ // Check for edit/delete as well
167
+ const editSelector = EDIT_SELECTORS.join(', ');
168
+ const deleteSelector = DELETE_SELECTORS.join(', ');
169
+
170
+ let hasEditOrDelete = false;
171
+ try {
172
+ const editCount = await Promise.race([
173
+ page.locator(editSelector).count(),
174
+ new Promise<number>((_, reject) =>
175
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
176
+ ),
177
+ ]) as number;
178
+
179
+ const deleteCount = await Promise.race([
180
+ page.locator(deleteSelector).count(),
181
+ new Promise<number>((_, reject) =>
182
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
183
+ ),
184
+ ]) as number;
185
+
186
+ hasEditOrDelete = editCount > 0 || deleteCount > 0;
187
+ } catch {
188
+ // Timeout or error
189
+ }
190
+
191
+ if (!hasEditOrDelete) {
192
+ findings.push({
193
+ id: `${this.agentId}-no-crud-${moduleId}`,
194
+ type: 'test-bug',
195
+ severity: 'low',
196
+ agentId: this.agentId,
197
+ module: moduleId,
198
+ description: `No CRUD actions found for module ${moduleId}`,
199
+ });
200
+ return findings;
201
+ }
202
+ }
203
+
204
+ // Attempt create flow
205
+ if (hasCreateButton) {
206
+ try {
207
+ await Promise.race([
208
+ page.locator(createSelector).first().click(),
209
+ new Promise((_, reject) =>
210
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
211
+ ),
212
+ ]);
213
+
214
+ // Look for form or modal
215
+ const formCount = await page.locator('form, [role="dialog"], .modal').count();
216
+ if (formCount > 0) {
217
+ // Fill text inputs with test data
218
+ const inputs = page.locator('form input[type="text"], [role="dialog"] input[type="text"]');
219
+ const inputCount = await inputs.count();
220
+ for (let i = 0; i < inputCount; i++) {
221
+ await inputs.nth(i).fill(`Test Data ${i + 1}`).catch(() => undefined);
222
+ }
223
+
224
+ // Try submit
225
+ const submitBtn = page.locator(
226
+ 'button[type="submit"], button:has-text("Save"), button:has-text("Submit"), button:has-text("Create")',
227
+ );
228
+ if (await submitBtn.count() > 0) {
229
+ await submitBtn.first().click().catch(() => undefined);
230
+
231
+ // Check for success indicator
232
+ const successSelector = SUCCESS_SELECTORS.join(', ');
233
+ try {
234
+ await page.waitForSelector(successSelector, { timeout: OP_TIMEOUT_MS });
235
+ } catch {
236
+ findings.push({
237
+ id: `${this.agentId}-create-no-feedback-${moduleId}`,
238
+ type: 'test-bug',
239
+ severity: 'medium',
240
+ agentId: this.agentId,
241
+ module: moduleId,
242
+ description: `Create action in module ${moduleId} did not produce a visible success/error response`,
243
+ });
244
+ }
245
+ }
246
+ }
247
+ } catch (createError) {
248
+ findings.push({
249
+ id: `${this.agentId}-create-failed-${moduleId}`,
250
+ type: 'test-bug',
251
+ severity: 'medium',
252
+ agentId: this.agentId,
253
+ module: moduleId,
254
+ description: `Create action failed for module ${moduleId}: ${createError instanceof Error ? createError.message.split('\n')[0] : String(createError)}`,
255
+ });
256
+ }
257
+ }
258
+
259
+ // Check for delete buttons on the page
260
+ const deleteSelector = DELETE_SELECTORS.join(', ');
261
+ try {
262
+ const deleteCount = await Promise.race([
263
+ page.locator(deleteSelector).count(),
264
+ new Promise<number>((_, reject) =>
265
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
266
+ ),
267
+ ]) as number;
268
+
269
+ if (deleteCount > 0) {
270
+ try {
271
+ await Promise.race([
272
+ page.locator(deleteSelector).first().click(),
273
+ new Promise((_, reject) =>
274
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
275
+ ),
276
+ ]);
277
+
278
+ // Look for confirm dialog
279
+ const confirmBtn = page.locator(
280
+ 'button:has-text("Confirm"), button:has-text("Yes"), button:has-text("OK")',
281
+ );
282
+ if (await confirmBtn.count() > 0) {
283
+ await confirmBtn.first().click().catch(() => undefined);
284
+ }
285
+ } catch (deleteError) {
286
+ findings.push({
287
+ id: `${this.agentId}-delete-failed-${moduleId}`,
288
+ type: 'test-bug',
289
+ severity: 'medium',
290
+ agentId: this.agentId,
291
+ module: moduleId,
292
+ description: `Delete action failed for module ${moduleId}: ${deleteError instanceof Error ? deleteError.message.split('\n')[0] : String(deleteError)}`,
293
+ });
294
+ }
295
+ }
296
+ } catch {
297
+ // Timeout discovering delete buttons — non-critical
298
+ }
299
+
300
+ return findings;
301
+ }
302
+ }
@@ -0,0 +1,297 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Finding } from '../core/types';
3
+ import { BaseAgent } from './base-agent';
4
+ import { resolveEnvironment } from '../helpers/env-resolver';
5
+ import { login } from '../helpers/navigation';
6
+
7
+ /** Timeout (ms) for individual form test operations. */
8
+ const OP_TIMEOUT_MS = 5_000;
9
+
10
+ /** Selectors that indicate validation error messages. */
11
+ const VALIDATION_ERROR_SELECTORS = [
12
+ '.error',
13
+ '[aria-invalid="true"]',
14
+ '.text-red',
15
+ '[class*="error"]',
16
+ '[role="alert"]',
17
+ '.field-error',
18
+ '.validation-error',
19
+ ] as const;
20
+
21
+ export class FormValidatorAgent extends BaseAgent {
22
+ readonly agentId = 26;
23
+ readonly agentName = 'Form Validator';
24
+ private baseUrl = '';
25
+
26
+ protected async preFlight(): Promise<void> {
27
+ if (!this.config.modules || this.config.modules.length === 0) {
28
+ throw new Error('FormValidatorAgent requires at least one module in config.modules');
29
+ }
30
+
31
+ const { env } = await resolveEnvironment(this.config, this.phase);
32
+ this.baseUrl = env.baseUrl;
33
+ }
34
+
35
+ protected async execute(): Promise<Finding[]> {
36
+ const findings: Finding[] = [];
37
+
38
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
39
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
40
+ const credentials =
41
+ this.config.auth.credentials?.['admin'] ??
42
+ Object.values(this.config.auth.credentials ?? {})[0];
43
+
44
+ if (!credentials) {
45
+ findings.push({
46
+ id: `${this.agentId}-no-credentials`,
47
+ type: 'infra-issue',
48
+ severity: 'medium',
49
+ agentId: this.agentId,
50
+ module: 'form-validator',
51
+ description: 'No credentials configured — continuing without authentication',
52
+ });
53
+ }
54
+
55
+ const { chromium } = await import('playwright');
56
+
57
+ let browser: Browser | null = null;
58
+
59
+ try {
60
+ browser = await chromium.launch({ headless: true });
61
+ const page = await browser.newPage();
62
+
63
+ if (credentials) {
64
+ try {
65
+ await login(page, credentials, loginUrl);
66
+ } catch (loginError) {
67
+ findings.push({
68
+ id: `${this.agentId}-login-failed`,
69
+ type: 'infra-issue',
70
+ severity: 'medium',
71
+ agentId: this.agentId,
72
+ module: 'form-validator',
73
+ description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
74
+ });
75
+ }
76
+ }
77
+
78
+ for (const mod of this.config.modules) {
79
+ const moduleFindings = await this.testModuleForms(page, mod.id, mod.route);
80
+ findings.push(...moduleFindings);
81
+ }
82
+
83
+ await page.close().catch(() => undefined);
84
+ } catch (playwrightError) {
85
+ findings.push({
86
+ id: `${this.agentId}-playwright-failure`,
87
+ type: 'infra-issue',
88
+ severity: 'medium',
89
+ agentId: this.agentId,
90
+ module: 'form-validator',
91
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
92
+ });
93
+ } finally {
94
+ if (browser) await browser.close().catch(() => undefined);
95
+ }
96
+
97
+ return findings;
98
+ }
99
+
100
+ private async testModuleForms(
101
+ page: Page,
102
+ moduleId: string,
103
+ moduleRoute: string,
104
+ ): Promise<Finding[]> {
105
+ const findings: Finding[] = [];
106
+ const fullUrl = moduleRoute.startsWith('http')
107
+ ? moduleRoute
108
+ : `${this.baseUrl}${moduleRoute}`;
109
+
110
+ try {
111
+ await Promise.race([
112
+ page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
113
+ new Promise((_, reject) =>
114
+ setTimeout(() => reject(new Error('Navigation timeout')), OP_TIMEOUT_MS),
115
+ ),
116
+ ]);
117
+ } catch {
118
+ findings.push({
119
+ id: `${this.agentId}-nav-failed-${moduleId}`,
120
+ type: 'test-bug',
121
+ severity: 'medium',
122
+ agentId: this.agentId,
123
+ module: moduleId,
124
+ description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
125
+ });
126
+ return findings;
127
+ }
128
+
129
+ // Find all forms
130
+ let formCount = 0;
131
+ try {
132
+ formCount = await Promise.race([
133
+ page.locator('form').count(),
134
+ new Promise<number>((_, reject) =>
135
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
136
+ ),
137
+ ]) as number;
138
+ } catch {
139
+ // Timeout discovering forms
140
+ }
141
+
142
+ if (formCount === 0) {
143
+ findings.push({
144
+ id: `${this.agentId}-no-forms-${moduleId}`,
145
+ type: 'test-bug',
146
+ severity: 'low',
147
+ agentId: this.agentId,
148
+ module: moduleId,
149
+ description: `No forms found in module ${moduleId}`,
150
+ });
151
+ return findings;
152
+ }
153
+
154
+ for (let formIdx = 0; formIdx < formCount; formIdx++) {
155
+ const formFindings = await this.testSingleForm(page, moduleId, formIdx);
156
+ findings.push(...formFindings);
157
+ }
158
+
159
+ return findings;
160
+ }
161
+
162
+ private async testSingleForm(
163
+ page: Page,
164
+ moduleId: string,
165
+ formIndex: number,
166
+ ): Promise<Finding[]> {
167
+ const findings: Finding[] = [];
168
+ const form = page.locator('form').nth(formIndex);
169
+
170
+ // Count inputs
171
+ let inputCount = 0;
172
+ try {
173
+ inputCount = await Promise.race([
174
+ form.locator('input, textarea, select').count(),
175
+ new Promise<number>((_, reject) =>
176
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
177
+ ),
178
+ ]) as number;
179
+ } catch {
180
+ return findings;
181
+ }
182
+
183
+ if (inputCount === 0) {
184
+ return findings;
185
+ }
186
+
187
+ // Empty submit test: click submit without filling anything
188
+ const submitBtn = form.locator(
189
+ 'button[type="submit"], input[type="submit"], button:has-text("Submit"), button:has-text("Save")',
190
+ );
191
+
192
+ let hasSubmitButton = false;
193
+ try {
194
+ hasSubmitButton = (await submitBtn.count()) > 0;
195
+ } catch {
196
+ return findings;
197
+ }
198
+
199
+ if (!hasSubmitButton) {
200
+ return findings;
201
+ }
202
+
203
+ try {
204
+ await Promise.race([
205
+ submitBtn.first().click(),
206
+ new Promise((_, reject) =>
207
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
208
+ ),
209
+ ]);
210
+
211
+ // Wait briefly for validation errors to appear
212
+ await page.waitForTimeout(500);
213
+
214
+ // Check for validation errors
215
+ const errorSelector = VALIDATION_ERROR_SELECTORS.join(', ');
216
+ let errorCount = 0;
217
+ try {
218
+ errorCount = await page.locator(errorSelector).count();
219
+ } catch {
220
+ // Error counting validation indicators
221
+ }
222
+
223
+ if (errorCount === 0) {
224
+ findings.push({
225
+ id: `${this.agentId}-empty-submit-${moduleId}-form${formIndex}`,
226
+ type: 'test-bug',
227
+ severity: 'high',
228
+ agentId: this.agentId,
229
+ module: moduleId,
230
+ description: `Form ${formIndex} in module ${moduleId} accepts empty submission without validation`,
231
+ });
232
+ }
233
+ } catch (submitError) {
234
+ findings.push({
235
+ id: `${this.agentId}-submit-error-${moduleId}-form${formIndex}`,
236
+ type: 'test-bug',
237
+ severity: 'medium',
238
+ agentId: this.agentId,
239
+ module: moduleId,
240
+ description: `Form ${formIndex} submit test failed in module ${moduleId}: ${submitError instanceof Error ? submitError.message.split('\n')[0] : String(submitError)}`,
241
+ });
242
+ }
243
+
244
+ // Valid submit test: fill required inputs with test data
245
+ try {
246
+ const inputs = form.locator('input, textarea, select');
247
+ const count = await inputs.count();
248
+ for (let i = 0; i < count; i++) {
249
+ const input = inputs.nth(i);
250
+ const tagName = await input.evaluate((el) => el.tagName.toLowerCase());
251
+ const inputType = await input.getAttribute('type') ?? 'text';
252
+
253
+ if (tagName === 'select') {
254
+ // Select second option if available
255
+ const optionCount = await input.locator('option').count();
256
+ if (optionCount > 1) {
257
+ const value = await input.locator('option').nth(1).getAttribute('value');
258
+ if (value) {
259
+ await input.selectOption(value).catch(() => undefined);
260
+ }
261
+ }
262
+ } else if (inputType === 'email') {
263
+ await input.fill('test@example.com').catch(() => undefined);
264
+ } else if (inputType === 'number') {
265
+ await input.fill('42').catch(() => undefined);
266
+ } else if (inputType === 'tel') {
267
+ await input.fill('+1234567890').catch(() => undefined);
268
+ } else if (inputType === 'checkbox' || inputType === 'radio') {
269
+ await input.check().catch(() => undefined);
270
+ } else if (tagName === 'textarea' || inputType === 'text' || inputType === 'search') {
271
+ await input.fill('Test input data').catch(() => undefined);
272
+ } else if (inputType === 'password') {
273
+ await input.fill('TestPassword123!').catch(() => undefined);
274
+ }
275
+ }
276
+
277
+ // Submit with filled data
278
+ await Promise.race([
279
+ submitBtn.first().click(),
280
+ new Promise((_, reject) =>
281
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
282
+ ),
283
+ ]);
284
+ } catch (validSubmitError) {
285
+ findings.push({
286
+ id: `${this.agentId}-valid-submit-error-${moduleId}-form${formIndex}`,
287
+ type: 'test-bug',
288
+ severity: 'medium',
289
+ agentId: this.agentId,
290
+ module: moduleId,
291
+ description: `Valid submission test failed for form ${formIndex} in module ${moduleId}: ${validSubmitError instanceof Error ? validSubmitError.message.split('\n')[0] : String(validSubmitError)}`,
292
+ });
293
+ }
294
+
295
+ return findings;
296
+ }
297
+ }