@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,194 @@
1
+ import type { Browser } 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 for waiting for sync. */
8
+ const SYNC_WAIT_MS = 5_000;
9
+
10
+ export class RealtimeSyncTesterAgent extends BaseAgent {
11
+ readonly agentId = 55;
12
+ readonly agentName = 'Realtime Sync Tester';
13
+ private baseUrl = '';
14
+
15
+ protected async preFlight(): Promise<void> {
16
+ if (!this.config.modules || this.config.modules.length === 0) {
17
+ throw new Error('RealtimeSyncTesterAgent requires at least one module in config.modules');
18
+ }
19
+
20
+ const { env } = await resolveEnvironment(this.config, this.phase);
21
+ this.baseUrl = env.baseUrl;
22
+ }
23
+
24
+ protected async execute(): Promise<Finding[]> {
25
+ const findings: Finding[] = [];
26
+
27
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
28
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
29
+ const credentials =
30
+ this.config.auth.credentials?.['admin'] ??
31
+ Object.values(this.config.auth.credentials ?? {})[0];
32
+
33
+ if (!credentials) {
34
+ findings.push({
35
+ id: `${this.agentId}-no-credentials`,
36
+ type: 'infra-issue',
37
+ severity: 'medium',
38
+ agentId: this.agentId,
39
+ module: 'realtime-sync-tester',
40
+ description: 'No credentials configured — realtime sync test requires authentication',
41
+ });
42
+ return findings;
43
+ }
44
+
45
+ const { chromium } = await import('playwright');
46
+
47
+ let browser: Browser | null = null;
48
+
49
+ try {
50
+ browser = await chromium.launch({ headless: true });
51
+
52
+ // Create two contexts
53
+ const context1 = await browser.newContext();
54
+ const context2 = await browser.newContext();
55
+ const page1 = await context1.newPage();
56
+ const page2 = await context2.newPage();
57
+
58
+ // Login both contexts
59
+ try {
60
+ await login(page1, credentials, loginUrl);
61
+ await login(page2, credentials, loginUrl);
62
+ } catch {
63
+ findings.push({
64
+ id: `${this.agentId}-login-failed`,
65
+ type: 'infra-issue',
66
+ severity: 'medium',
67
+ agentId: this.agentId,
68
+ module: 'realtime-sync-tester',
69
+ description: 'Login failed for one or both contexts',
70
+ });
71
+ await page1.close().catch(() => undefined);
72
+ await page2.close().catch(() => undefined);
73
+ await context1.close().catch(() => undefined);
74
+ await context2.close().catch(() => undefined);
75
+ return findings;
76
+ }
77
+
78
+ const firstModule = this.config.modules[0];
79
+ const fullUrl = firstModule.route.startsWith('http')
80
+ ? firstModule.route
81
+ : `${this.baseUrl}${firstModule.route}`;
82
+
83
+ // Navigate both pages to the same module
84
+ try {
85
+ await page1.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
86
+ await page2.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
87
+ } catch {
88
+ findings.push({
89
+ id: `${this.agentId}-nav-failed`,
90
+ type: 'test-bug',
91
+ severity: 'medium',
92
+ agentId: this.agentId,
93
+ module: firstModule.id,
94
+ description: `Failed to navigate both contexts to ${firstModule.id}`,
95
+ });
96
+ await page1.close().catch(() => undefined);
97
+ await page2.close().catch(() => undefined);
98
+ await context1.close().catch(() => undefined);
99
+ await context2.close().catch(() => undefined);
100
+ return findings;
101
+ }
102
+
103
+ // Get initial item count on context2
104
+ const initialCount = await this.getItemCount(page2);
105
+
106
+ // Context1: try to create an item
107
+ const createSelectors = 'button:has-text("Create"), button:has-text("Add"), button:has-text("New"), [data-testid*="create"], [data-testid*="add"]';
108
+ const createCount = await page1.locator(createSelectors).count();
109
+
110
+ if (createCount === 0) {
111
+ findings.push({
112
+ id: `${this.agentId}-no-create-button`,
113
+ type: 'test-bug',
114
+ severity: 'low',
115
+ agentId: this.agentId,
116
+ module: firstModule.id,
117
+ description: `No create/add button found in ${firstModule.id} — cannot test realtime sync`,
118
+ });
119
+ } else {
120
+ try {
121
+ await page1.locator(createSelectors).first().click();
122
+
123
+ // Fill any text inputs in form/dialog
124
+ const inputs = page1.locator('form input[type="text"], [role="dialog"] input[type="text"]');
125
+ const inputCount = await inputs.count();
126
+ for (let i = 0; i < inputCount; i++) {
127
+ await inputs.nth(i).fill(`Sync Test ${Date.now()}`).catch(() => undefined);
128
+ }
129
+
130
+ // Submit
131
+ const submitBtn = page1.locator('button[type="submit"], button:has-text("Save"), button:has-text("Submit")');
132
+ if (await submitBtn.count() > 0) {
133
+ await submitBtn.first().click().catch(() => undefined);
134
+ }
135
+
136
+ // Wait for sync
137
+ await new Promise(resolve => setTimeout(resolve, SYNC_WAIT_MS));
138
+
139
+ // Check context2 for new item (WITHOUT refresh)
140
+ const newCount = await this.getItemCount(page2);
141
+
142
+ if (newCount <= initialCount) {
143
+ findings.push({
144
+ id: `${this.agentId}-no-realtime-sync-${firstModule.id}`,
145
+ type: 'test-bug',
146
+ severity: 'low',
147
+ agentId: this.agentId,
148
+ module: firstModule.id,
149
+ description: `New item created in context 1 not visible in context 2 without refresh — realtime sync not implemented`,
150
+ });
151
+ }
152
+ } catch (createError) {
153
+ findings.push({
154
+ id: `${this.agentId}-create-failed`,
155
+ type: 'test-bug',
156
+ severity: 'medium',
157
+ agentId: this.agentId,
158
+ module: firstModule.id,
159
+ description: `Create action failed during sync test: ${createError instanceof Error ? createError.message.split('\n')[0] : String(createError)}`,
160
+ });
161
+ }
162
+ }
163
+
164
+ await page1.close().catch(() => undefined);
165
+ await page2.close().catch(() => undefined);
166
+ await context1.close().catch(() => undefined);
167
+ await context2.close().catch(() => undefined);
168
+ } catch (playwrightError) {
169
+ findings.push({
170
+ id: `${this.agentId}-playwright-failure`,
171
+ type: 'infra-issue',
172
+ severity: 'medium',
173
+ agentId: this.agentId,
174
+ module: 'realtime-sync-tester',
175
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
176
+ });
177
+ } finally {
178
+ if (browser) await browser.close().catch(() => undefined);
179
+ }
180
+
181
+ return findings;
182
+ }
183
+
184
+ private async getItemCount(page: import('playwright').Page): Promise<number> {
185
+ try {
186
+ return await page.evaluate(() => {
187
+ const rows = document.querySelectorAll('tr, [role="row"], li, [data-testid*="item"], [data-testid*="row"]');
188
+ return rows.length;
189
+ });
190
+ } catch {
191
+ return 0;
192
+ }
193
+ }
194
+ }
@@ -0,0 +1,194 @@
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
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import * as os from 'node:os';
9
+
10
+ export class FileUploadTesterAgent extends BaseAgent {
11
+ readonly agentId = 56;
12
+ readonly agentName = 'File Upload Tester';
13
+ private baseUrl = '';
14
+
15
+ protected async preFlight(): Promise<void> {
16
+ if (!this.config.modules || this.config.modules.length === 0) {
17
+ throw new Error('FileUploadTesterAgent requires at least one module in config.modules');
18
+ }
19
+
20
+ const { env } = await resolveEnvironment(this.config, this.phase);
21
+ this.baseUrl = env.baseUrl;
22
+ }
23
+
24
+ protected async execute(): Promise<Finding[]> {
25
+ const findings: Finding[] = [];
26
+
27
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
28
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
29
+ const credentials =
30
+ this.config.auth.credentials?.['admin'] ??
31
+ Object.values(this.config.auth.credentials ?? {})[0];
32
+
33
+ const { chromium } = await import('playwright');
34
+
35
+ let browser: Browser | null = null;
36
+ const tmpDir = os.tmpdir();
37
+
38
+ try {
39
+ browser = await chromium.launch({ headless: true });
40
+ const page = await browser.newPage();
41
+
42
+ if (credentials) {
43
+ try {
44
+ await login(page, credentials, loginUrl);
45
+ } catch {
46
+ findings.push({
47
+ id: `${this.agentId}-login-failed`,
48
+ type: 'infra-issue',
49
+ severity: 'medium',
50
+ agentId: this.agentId,
51
+ module: 'file-upload-tester',
52
+ description: 'Login failed — continuing anonymously',
53
+ });
54
+ }
55
+ }
56
+
57
+ for (const mod of this.config.modules) {
58
+ const moduleFindings = await this.testModuleUploads(page, mod.id, mod.route, tmpDir);
59
+ findings.push(...moduleFindings);
60
+ }
61
+
62
+ await page.close().catch(() => undefined);
63
+ } catch (playwrightError) {
64
+ findings.push({
65
+ id: `${this.agentId}-playwright-failure`,
66
+ type: 'infra-issue',
67
+ severity: 'medium',
68
+ agentId: this.agentId,
69
+ module: 'file-upload-tester',
70
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
71
+ });
72
+ } finally {
73
+ if (browser) await browser.close().catch(() => undefined);
74
+ // Cleanup temp files
75
+ this.cleanupTempFiles(tmpDir);
76
+ }
77
+
78
+ return findings;
79
+ }
80
+
81
+ private async testModuleUploads(
82
+ page: Page,
83
+ moduleId: string,
84
+ moduleRoute: string,
85
+ tmpDir: string,
86
+ ): Promise<Finding[]> {
87
+ const findings: Finding[] = [];
88
+ const fullUrl = moduleRoute.startsWith('http')
89
+ ? moduleRoute
90
+ : `${this.baseUrl}${moduleRoute}`;
91
+
92
+ try {
93
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
94
+ } catch {
95
+ return findings; // Skip if navigation fails
96
+ }
97
+
98
+ // Find file inputs
99
+ const fileInputCount = await page.locator('input[type="file"]').count();
100
+
101
+ if (fileInputCount === 0) {
102
+ return findings; // No file upload on this page
103
+ }
104
+
105
+ const fileInput = page.locator('input[type="file"]').first();
106
+
107
+ // Test 1: Upload a valid text file
108
+ const validFilePath = path.join(tmpDir, 'test-upload.txt');
109
+ fs.writeFileSync(validFilePath, 'Test file content for upload testing');
110
+
111
+ try {
112
+ await fileInput.setInputFiles(validFilePath);
113
+
114
+ // Check for success feedback
115
+ await new Promise(resolve => setTimeout(resolve, 2_000));
116
+ const successIndicators = await page.locator('[class*="toast"], [role="alert"], .success, .notification').count();
117
+
118
+ if (successIndicators === 0) {
119
+ findings.push({
120
+ id: `${this.agentId}-no-upload-feedback-${moduleId}`,
121
+ type: 'test-bug',
122
+ severity: 'low',
123
+ agentId: this.agentId,
124
+ module: moduleId,
125
+ description: `No visible feedback after file upload in ${moduleId}`,
126
+ });
127
+ }
128
+ } catch {
129
+ // Upload attempt failed — non-critical
130
+ }
131
+
132
+ // Test 2: Try uploading .exe file — should be rejected
133
+ const exeFilePath = path.join(tmpDir, 'malicious.exe');
134
+ fs.writeFileSync(exeFilePath, 'MZ fake exe content');
135
+
136
+ try {
137
+ // Reset file input by navigating back
138
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
139
+ const newFileInput = page.locator('input[type="file"]').first();
140
+ await newFileInput.setInputFiles(exeFilePath);
141
+
142
+ await new Promise(resolve => setTimeout(resolve, 2_000));
143
+
144
+ // Check if exe was accepted (it should be rejected)
145
+ const errorIndicators = await page.locator('[class*="error"], [role="alert"], .error, .danger').count();
146
+ if (errorIndicators === 0) {
147
+ findings.push({
148
+ id: `${this.agentId}-exe-not-rejected-${moduleId}`,
149
+ type: 'code-bug-security',
150
+ severity: 'high',
151
+ agentId: this.agentId,
152
+ module: moduleId,
153
+ description: `Executable file (.exe) upload was not rejected in ${moduleId}`,
154
+ suggestedFix: 'Implement file type validation to reject executable files',
155
+ });
156
+ }
157
+ } catch {
158
+ // Upload rejection via exception is acceptable
159
+ }
160
+
161
+ // Test 3: Try path traversal filename
162
+ const traversalFilePath = path.join(tmpDir, 'test-traversal.txt');
163
+ fs.writeFileSync(traversalFilePath, 'path traversal test');
164
+
165
+ try {
166
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
167
+ const traversalInput = page.locator('input[type="file"]').first();
168
+ await traversalInput.setInputFiles(traversalFilePath);
169
+
170
+ // We can't actually set the filename to ../../etc/passwd via Playwright,
171
+ // but we document that the test checks for server-side validation
172
+ this.addEvidence({
173
+ type: 'log',
174
+ path: '',
175
+ description: `File upload path traversal test performed for ${moduleId}`,
176
+ });
177
+ } catch {
178
+ // Non-critical
179
+ }
180
+
181
+ return findings;
182
+ }
183
+
184
+ private cleanupTempFiles(tmpDir: string): void {
185
+ const files = ['test-upload.txt', 'malicious.exe', 'test-traversal.txt'];
186
+ for (const file of files) {
187
+ try {
188
+ fs.unlinkSync(path.join(tmpDir, file));
189
+ } catch {
190
+ // Cleanup failure — non-critical
191
+ }
192
+ }
193
+ }
194
+ }
@@ -0,0 +1,174 @@
1
+ import type { Browser, Page, Download } 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
+ /** Selectors for export/download buttons. */
8
+ const EXPORT_SELECTORS = [
9
+ 'button:has-text("Export")',
10
+ 'button:has-text("Download")',
11
+ 'button:has-text("CSV")',
12
+ 'button:has-text("PDF")',
13
+ 'button:has-text("Excel")',
14
+ 'a:has-text("Export")',
15
+ 'a:has-text("Download")',
16
+ '[data-testid*="export"]',
17
+ '[data-testid*="download"]',
18
+ ] as const;
19
+
20
+ export class ExportTesterAgent extends BaseAgent {
21
+ readonly agentId = 57;
22
+ readonly agentName = 'Export Tester';
23
+ private baseUrl = '';
24
+
25
+ protected async preFlight(): Promise<void> {
26
+ if (!this.config.modules || this.config.modules.length === 0) {
27
+ throw new Error('ExportTesterAgent requires at least one module in config.modules');
28
+ }
29
+
30
+ const { env } = await resolveEnvironment(this.config, this.phase);
31
+ this.baseUrl = env.baseUrl;
32
+ }
33
+
34
+ protected async execute(): Promise<Finding[]> {
35
+ const findings: Finding[] = [];
36
+
37
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
38
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
39
+ const credentials =
40
+ this.config.auth.credentials?.['admin'] ??
41
+ Object.values(this.config.auth.credentials ?? {})[0];
42
+
43
+ const { chromium } = await import('playwright');
44
+
45
+ let browser: Browser | null = null;
46
+
47
+ try {
48
+ browser = await chromium.launch({ headless: true });
49
+ const page = await browser.newPage();
50
+
51
+ if (credentials) {
52
+ try {
53
+ await login(page, credentials, loginUrl);
54
+ } catch {
55
+ findings.push({
56
+ id: `${this.agentId}-login-failed`,
57
+ type: 'infra-issue',
58
+ severity: 'medium',
59
+ agentId: this.agentId,
60
+ module: 'export-tester',
61
+ description: 'Login failed — continuing anonymously',
62
+ });
63
+ }
64
+ }
65
+
66
+ for (const mod of this.config.modules) {
67
+ const moduleFindings = await this.testModuleExports(page, mod.id, mod.route);
68
+ findings.push(...moduleFindings);
69
+ }
70
+
71
+ await page.close().catch(() => undefined);
72
+ } catch (playwrightError) {
73
+ findings.push({
74
+ id: `${this.agentId}-playwright-failure`,
75
+ type: 'infra-issue',
76
+ severity: 'medium',
77
+ agentId: this.agentId,
78
+ module: 'export-tester',
79
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
80
+ });
81
+ } finally {
82
+ if (browser) await browser.close().catch(() => undefined);
83
+ }
84
+
85
+ return findings;
86
+ }
87
+
88
+ private async testModuleExports(
89
+ page: Page,
90
+ moduleId: string,
91
+ moduleRoute: string,
92
+ ): Promise<Finding[]> {
93
+ const findings: Finding[] = [];
94
+ const fullUrl = moduleRoute.startsWith('http')
95
+ ? moduleRoute
96
+ : `${this.baseUrl}${moduleRoute}`;
97
+
98
+ try {
99
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
100
+ } catch {
101
+ return findings;
102
+ }
103
+
104
+ const selector = EXPORT_SELECTORS.join(', ');
105
+ const exportBtnCount = await page.locator(selector).count();
106
+
107
+ if (exportBtnCount === 0) {
108
+ return findings; // No export buttons on this page
109
+ }
110
+
111
+ // Test first export button
112
+ try {
113
+ const downloadPromise = page.waitForEvent('download', { timeout: 10_000 });
114
+ await page.locator(selector).first().click();
115
+
116
+ let download: Download | null = null;
117
+ try {
118
+ download = await downloadPromise;
119
+ } catch {
120
+ findings.push({
121
+ id: `${this.agentId}-no-download-${moduleId}`,
122
+ type: 'test-bug',
123
+ severity: 'medium',
124
+ agentId: this.agentId,
125
+ module: moduleId,
126
+ description: `Export button clicked but no download initiated in ${moduleId}`,
127
+ });
128
+ return findings;
129
+ }
130
+
131
+ if (download) {
132
+ const suggestedFilename = download.suggestedFilename();
133
+
134
+ // Save and check file size
135
+ try {
136
+ const filePath = await download.path();
137
+ if (filePath) {
138
+ const fs = await import('node:fs');
139
+ const stats = fs.statSync(filePath);
140
+ if (stats.size === 0) {
141
+ findings.push({
142
+ id: `${this.agentId}-empty-download-${moduleId}`,
143
+ type: 'test-bug',
144
+ severity: 'medium',
145
+ agentId: this.agentId,
146
+ module: moduleId,
147
+ description: `Downloaded file "${suggestedFilename}" is empty (0 bytes) in ${moduleId}`,
148
+ });
149
+ } else {
150
+ this.addEvidence({
151
+ type: 'log',
152
+ path: '',
153
+ description: `Export successful: "${suggestedFilename}" (${stats.size} bytes) from ${moduleId}`,
154
+ });
155
+ }
156
+ }
157
+ } catch {
158
+ // File access failure — non-critical
159
+ }
160
+ }
161
+ } catch (clickError) {
162
+ findings.push({
163
+ id: `${this.agentId}-export-click-failed-${moduleId}`,
164
+ type: 'test-bug',
165
+ severity: 'medium',
166
+ agentId: this.agentId,
167
+ module: moduleId,
168
+ description: `Export button click failed in ${moduleId}: ${clickError instanceof Error ? clickError.message.split('\n')[0] : String(clickError)}`,
169
+ });
170
+ }
171
+
172
+ return findings;
173
+ }
174
+ }