@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,350 @@
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 responsive interaction tests. */
8
+ const OP_TIMEOUT_MS = 3_000;
9
+
10
+ /** Breakpoints to test: mobile, tablet, desktop. */
11
+ const BREAKPOINTS = [
12
+ { name: 'mobile', width: 375, height: 812 },
13
+ { name: 'tablet', width: 768, height: 1024 },
14
+ { name: 'desktop', width: 1280, height: 800 },
15
+ ] as const;
16
+
17
+ /** Selectors for hamburger/menu toggle buttons. */
18
+ const HAMBURGER_SELECTORS = [
19
+ 'button[aria-label*="menu" i]',
20
+ '.hamburger',
21
+ '[data-testid*="menu-toggle"]',
22
+ 'button:has(.ri-menu-line)',
23
+ ] as const;
24
+
25
+ /** WCAG minimum touch target size in pixels. */
26
+ const MIN_TOUCH_TARGET_PX = 44;
27
+
28
+ export class ResponsiveInteractionTesterAgent extends BaseAgent {
29
+ readonly agentId = 29;
30
+ readonly agentName = 'Responsive Interaction Tester';
31
+ private baseUrl = '';
32
+
33
+ protected async preFlight(): Promise<void> {
34
+ if (!this.config.modules || this.config.modules.length === 0) {
35
+ throw new Error(
36
+ 'ResponsiveInteractionTesterAgent requires at least one module in config.modules',
37
+ );
38
+ }
39
+
40
+ const { env } = await resolveEnvironment(this.config, this.phase);
41
+ this.baseUrl = env.baseUrl;
42
+ }
43
+
44
+ protected async execute(): Promise<Finding[]> {
45
+ const findings: Finding[] = [];
46
+
47
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
48
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
49
+ const credentials =
50
+ this.config.auth.credentials?.['admin'] ??
51
+ Object.values(this.config.auth.credentials ?? {})[0];
52
+
53
+ if (!credentials) {
54
+ findings.push({
55
+ id: `${this.agentId}-no-credentials`,
56
+ type: 'infra-issue',
57
+ severity: 'medium',
58
+ agentId: this.agentId,
59
+ module: 'responsive-interaction-tester',
60
+ description: 'No credentials configured — continuing without authentication',
61
+ });
62
+ }
63
+
64
+ const { chromium } = await import('playwright');
65
+
66
+ let browser: Browser | null = null;
67
+
68
+ try {
69
+ browser = await chromium.launch({ headless: true });
70
+ const page = await browser.newPage();
71
+
72
+ // Login at desktop size first
73
+ if (credentials) {
74
+ try {
75
+ await login(page, credentials, loginUrl);
76
+ } catch (loginError) {
77
+ findings.push({
78
+ id: `${this.agentId}-login-failed`,
79
+ type: 'infra-issue',
80
+ severity: 'medium',
81
+ agentId: this.agentId,
82
+ module: 'responsive-interaction-tester',
83
+ description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
84
+ });
85
+ }
86
+ }
87
+
88
+ // Test first 3 modules to limit scope
89
+ const modulesToTest = this.config.modules.slice(0, 3);
90
+
91
+ for (const bp of BREAKPOINTS) {
92
+ await page.setViewportSize({ width: bp.width, height: bp.height });
93
+
94
+ for (const mod of modulesToTest) {
95
+ const fullUrl = mod.route.startsWith('http')
96
+ ? mod.route
97
+ : `${this.baseUrl}${mod.route}`;
98
+
99
+ try {
100
+ await Promise.race([
101
+ page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
102
+ new Promise((_, reject) =>
103
+ setTimeout(() => reject(new Error('Navigation timeout')), OP_TIMEOUT_MS),
104
+ ),
105
+ ]);
106
+ } catch {
107
+ continue; // Skip module if navigation fails
108
+ }
109
+
110
+ // Hamburger menu test (mobile/tablet only)
111
+ if (bp.name !== 'desktop') {
112
+ const hamburgerFindings = await this.testHamburgerMenu(page, bp.name, mod.id);
113
+ findings.push(...hamburgerFindings);
114
+ }
115
+
116
+ // Touch target test
117
+ const touchFindings = await this.testTouchTargets(page, bp.name, bp.width, mod.id);
118
+ findings.push(...touchFindings);
119
+
120
+ // Modal test
121
+ const modalFindings = await this.testModalOverflow(page, bp.name, bp.width, mod.id);
122
+ findings.push(...modalFindings);
123
+
124
+ // Horizontal scroll test
125
+ const scrollFindings = await this.testHorizontalScroll(page, bp.name, mod.id);
126
+ findings.push(...scrollFindings);
127
+ }
128
+ }
129
+
130
+ await page.close().catch(() => undefined);
131
+ } catch (playwrightError) {
132
+ findings.push({
133
+ id: `${this.agentId}-playwright-failure`,
134
+ type: 'infra-issue',
135
+ severity: 'medium',
136
+ agentId: this.agentId,
137
+ module: 'responsive-interaction-tester',
138
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
139
+ });
140
+ } finally {
141
+ if (browser) await browser.close().catch(() => undefined);
142
+ }
143
+
144
+ return findings;
145
+ }
146
+
147
+ /**
148
+ * Test hamburger menu: find toggle button, click it, verify nav panel opens.
149
+ */
150
+ private async testHamburgerMenu(
151
+ page: Page,
152
+ breakpoint: string,
153
+ moduleId: string,
154
+ ): Promise<Finding[]> {
155
+ const findings: Finding[] = [];
156
+ const selector = HAMBURGER_SELECTORS.join(', ');
157
+
158
+ try {
159
+ const hamburgerCount = await Promise.race([
160
+ page.locator(selector).count(),
161
+ new Promise<number>((_, reject) =>
162
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
163
+ ),
164
+ ]) as number;
165
+
166
+ if (hamburgerCount > 0) {
167
+ await Promise.race([
168
+ page.locator(selector).first().click(),
169
+ new Promise((_, reject) =>
170
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
171
+ ),
172
+ ]);
173
+
174
+ // Check if a navigation panel opened
175
+ const navPanel = await page
176
+ .locator('nav, [role="navigation"], [class*="sidebar"], [class*="drawer"]')
177
+ .count();
178
+
179
+ if (navPanel === 0) {
180
+ findings.push({
181
+ id: `${this.agentId}-hamburger-no-nav-${breakpoint}-${moduleId}`,
182
+ type: 'test-bug',
183
+ severity: 'medium',
184
+ agentId: this.agentId,
185
+ module: moduleId,
186
+ description: `Hamburger menu click did not open a navigation panel at ${breakpoint} in module ${moduleId}`,
187
+ });
188
+ }
189
+ }
190
+ } catch {
191
+ // Timeout — non-critical
192
+ }
193
+
194
+ return findings;
195
+ }
196
+
197
+ /**
198
+ * Test touch targets: find clickable elements with dimensions < 44px.
199
+ */
200
+ private async testTouchTargets(
201
+ page: Page,
202
+ breakpoint: string,
203
+ viewportWidth: number,
204
+ moduleId: string,
205
+ ): Promise<Finding[]> {
206
+ const findings: Finding[] = [];
207
+
208
+ try {
209
+ const smallTargets = await Promise.race([
210
+ page.evaluate((minSize: number) => {
211
+ const clickable = document.querySelectorAll('a, button, [role="button"], input, select');
212
+ const tooSmall: string[] = [];
213
+ for (const el of clickable) {
214
+ const rect = el.getBoundingClientRect();
215
+ if (rect.width > 0 && rect.height > 0) {
216
+ if (rect.width < minSize || rect.height < minSize) {
217
+ const tag = el.tagName.toLowerCase();
218
+ const id = el.id ? `#${el.id}` : '';
219
+ const cls = el.className
220
+ ? `.${String(el.className).split(' ')[0]}`
221
+ : '';
222
+ tooSmall.push(`${tag}${id}${cls} (${Math.round(rect.width)}x${Math.round(rect.height)})`);
223
+ }
224
+ }
225
+ }
226
+ return tooSmall.slice(0, 5); // Limit to first 5
227
+ }, MIN_TOUCH_TARGET_PX),
228
+ new Promise<string[]>((_, reject) =>
229
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
230
+ ),
231
+ ]) as string[];
232
+
233
+ if (smallTargets.length > 0) {
234
+ findings.push({
235
+ id: `${this.agentId}-touch-target-${breakpoint}-${moduleId}`,
236
+ type: 'test-bug',
237
+ severity: 'medium',
238
+ agentId: this.agentId,
239
+ module: moduleId,
240
+ description: `Touch target too small at ${viewportWidth}px in module ${moduleId}: ${smallTargets.join(', ')}`,
241
+ });
242
+ }
243
+ } catch {
244
+ // Timeout — non-critical
245
+ }
246
+
247
+ return findings;
248
+ }
249
+
250
+ /**
251
+ * Test modal overflow: look for a modal trigger, click it, check width.
252
+ */
253
+ private async testModalOverflow(
254
+ page: Page,
255
+ breakpoint: string,
256
+ viewportWidth: number,
257
+ moduleId: string,
258
+ ): Promise<Finding[]> {
259
+ const findings: Finding[] = [];
260
+
261
+ try {
262
+ const modalTrigger = page.locator(
263
+ '[data-testid*="modal"], button:has-text("Open"), button:has-text("Details")',
264
+ );
265
+
266
+ const triggerCount = await Promise.race([
267
+ modalTrigger.count(),
268
+ new Promise<number>((_, reject) =>
269
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
270
+ ),
271
+ ]) as number;
272
+
273
+ if (triggerCount > 0) {
274
+ await modalTrigger.first().click().catch(() => undefined);
275
+ await page.waitForTimeout(500);
276
+
277
+ const modalOverflow = await page
278
+ .evaluate((vw: number) => {
279
+ const modals = document.querySelectorAll(
280
+ '[role="dialog"], .modal, [class*="modal"], [data-testid*="modal"]',
281
+ );
282
+ for (const modal of modals) {
283
+ const rect = modal.getBoundingClientRect();
284
+ if (rect.width > vw) {
285
+ return true;
286
+ }
287
+ }
288
+ return false;
289
+ }, viewportWidth)
290
+ .catch(() => false);
291
+
292
+ if (modalOverflow) {
293
+ findings.push({
294
+ id: `${this.agentId}-modal-overflow-${breakpoint}-${moduleId}`,
295
+ type: 'test-bug',
296
+ severity: 'medium',
297
+ agentId: this.agentId,
298
+ module: moduleId,
299
+ description: `Modal overflows at ${viewportWidth}px in module ${moduleId}`,
300
+ });
301
+ }
302
+
303
+ // Close modal (press Escape)
304
+ await page.keyboard.press('Escape').catch(() => undefined);
305
+ }
306
+ } catch {
307
+ // Timeout — non-critical
308
+ }
309
+
310
+ return findings;
311
+ }
312
+
313
+ /**
314
+ * Test horizontal scrolling: check if scrollWidth > clientWidth.
315
+ */
316
+ private async testHorizontalScroll(
317
+ page: Page,
318
+ breakpoint: string,
319
+ moduleId: string,
320
+ ): Promise<Finding[]> {
321
+ const findings: Finding[] = [];
322
+
323
+ try {
324
+ const hasHorizontalScroll = await Promise.race([
325
+ page.evaluate(
326
+ () =>
327
+ document.documentElement.scrollWidth > document.documentElement.clientWidth,
328
+ ),
329
+ new Promise<boolean>((_, reject) =>
330
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
331
+ ),
332
+ ]) as boolean;
333
+
334
+ if (hasHorizontalScroll) {
335
+ findings.push({
336
+ id: `${this.agentId}-horizontal-scroll-${breakpoint}-${moduleId}`,
337
+ type: 'test-bug',
338
+ severity: 'high',
339
+ agentId: this.agentId,
340
+ module: moduleId,
341
+ description: `Horizontal scroll detected at ${breakpoint} in module ${moduleId}`,
342
+ });
343
+ }
344
+ } catch {
345
+ // Timeout — non-critical
346
+ }
347
+
348
+ return findings;
349
+ }
350
+ }
@@ -0,0 +1,319 @@
1
+ import type { Browser, BrowserContext } 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 multi-user operations. */
8
+ const OP_TIMEOUT_MS = 5_000;
9
+
10
+ export class MultiUserScenarioTesterAgent extends BaseAgent {
11
+ readonly agentId = 30;
12
+ readonly agentName = 'Multi-User Scenario Tester';
13
+ private baseUrl = '';
14
+
15
+ protected async preFlight(): Promise<void> {
16
+ const { env } = await resolveEnvironment(this.config, this.phase);
17
+ this.baseUrl = env.baseUrl;
18
+
19
+ // Check that we have at least some credentials
20
+ const credEntries = Object.values(this.config.auth.credentials ?? {});
21
+ if (credEntries.length === 0) {
22
+ throw new Error(
23
+ 'MultiUserScenarioTesterAgent requires at least one credential set in config.auth.credentials',
24
+ );
25
+ }
26
+ }
27
+
28
+ protected async execute(): Promise<Finding[]> {
29
+ const findings: Finding[] = [];
30
+
31
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
32
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
33
+
34
+ const credEntries = Object.values(this.config.auth.credentials ?? {});
35
+ const creds1 = credEntries[0];
36
+ const creds2 = credEntries.length > 1 ? credEntries[1] : credEntries[0];
37
+
38
+ const { chromium } = await import('playwright');
39
+
40
+ let browser: Browser | null = null;
41
+ let context1: BrowserContext | null = null;
42
+ let context2: BrowserContext | null = null;
43
+
44
+ try {
45
+ browser = await chromium.launch({ headless: true });
46
+ context1 = await browser.newContext();
47
+ context2 = await browser.newContext();
48
+
49
+ const page1 = await context1.newPage();
50
+ const page2 = await context2.newPage();
51
+
52
+ // Login user 1
53
+ let user1LoggedIn = false;
54
+ try {
55
+ await login(page1, creds1, loginUrl);
56
+ user1LoggedIn = true;
57
+ } catch (loginError) {
58
+ findings.push({
59
+ id: `${this.agentId}-user1-login-failed`,
60
+ type: 'infra-issue',
61
+ severity: 'medium',
62
+ agentId: this.agentId,
63
+ module: 'multi-user',
64
+ description: `User 1 login failed: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
65
+ });
66
+ }
67
+
68
+ // Login user 2
69
+ let user2LoggedIn = false;
70
+ try {
71
+ await login(page2, creds2, loginUrl);
72
+ user2LoggedIn = true;
73
+ } catch (loginError) {
74
+ findings.push({
75
+ id: `${this.agentId}-user2-login-failed`,
76
+ type: 'infra-issue',
77
+ severity: 'medium',
78
+ agentId: this.agentId,
79
+ module: 'multi-user',
80
+ description: `User 2 login failed: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
81
+ });
82
+ }
83
+
84
+ // If either login failed, return early
85
+ if (!user1LoggedIn || !user2LoggedIn) {
86
+ findings.push({
87
+ id: `${this.agentId}-login-incomplete`,
88
+ type: 'infra-issue',
89
+ severity: 'medium',
90
+ agentId: this.agentId,
91
+ module: 'multi-user',
92
+ description: 'One or both users failed to log in — skipping multi-user tests',
93
+ });
94
+ await page1.close().catch(() => undefined);
95
+ await page2.close().catch(() => undefined);
96
+ return findings;
97
+ }
98
+
99
+ const firstModule = this.config.modules?.[0];
100
+ const secondModule = this.config.modules?.[1];
101
+
102
+ if (!firstModule) {
103
+ findings.push({
104
+ id: `${this.agentId}-no-modules`,
105
+ type: 'infra-issue',
106
+ severity: 'medium',
107
+ agentId: this.agentId,
108
+ module: 'multi-user',
109
+ description: 'No modules configured — skipping multi-user tests',
110
+ });
111
+ await page1.close().catch(() => undefined);
112
+ await page2.close().catch(() => undefined);
113
+ return findings;
114
+ }
115
+
116
+ const mod1Url = firstModule.route.startsWith('http')
117
+ ? firstModule.route
118
+ : `${this.baseUrl}${firstModule.route}`;
119
+
120
+ // Test 1: Concurrent sessions
121
+ const concurrentFindings = await this.testConcurrentSessions(page1, page2, mod1Url);
122
+ findings.push(...concurrentFindings);
123
+
124
+ // Test 2: Same page access
125
+ const samePageFindings = await this.testSamePageAccess(page1, page2, mod1Url, firstModule.id);
126
+ findings.push(...samePageFindings);
127
+
128
+ // Test 3: Independent navigation (requires 2 modules)
129
+ if (secondModule) {
130
+ const mod2Url = secondModule.route.startsWith('http')
131
+ ? secondModule.route
132
+ : `${this.baseUrl}${secondModule.route}`;
133
+ const independentFindings = await this.testIndependentNavigation(
134
+ page1,
135
+ page2,
136
+ mod1Url,
137
+ mod2Url,
138
+ firstModule.id,
139
+ secondModule.id,
140
+ );
141
+ findings.push(...independentFindings);
142
+ }
143
+
144
+ await page1.close().catch(() => undefined);
145
+ await page2.close().catch(() => undefined);
146
+ } catch (playwrightError) {
147
+ findings.push({
148
+ id: `${this.agentId}-playwright-failure`,
149
+ type: 'infra-issue',
150
+ severity: 'medium',
151
+ agentId: this.agentId,
152
+ module: 'multi-user',
153
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
154
+ });
155
+ } finally {
156
+ if (context1) await context1.close().catch(() => undefined);
157
+ if (context2) await context2.close().catch(() => undefined);
158
+ if (browser) await browser.close().catch(() => undefined);
159
+ }
160
+
161
+ return findings;
162
+ }
163
+
164
+ /**
165
+ * Test 1: Both users navigate to the same module.
166
+ * Neither should be logged out (session conflict).
167
+ */
168
+ private async testConcurrentSessions(
169
+ page1: import('playwright').Page,
170
+ page2: import('playwright').Page,
171
+ moduleUrl: string,
172
+ ): Promise<Finding[]> {
173
+ const findings: Finding[] = [];
174
+
175
+ try {
176
+ await Promise.race([
177
+ Promise.all([
178
+ page1.goto(moduleUrl, { waitUntil: 'domcontentloaded' }),
179
+ page2.goto(moduleUrl, { waitUntil: 'domcontentloaded' }),
180
+ ]),
181
+ new Promise((_, reject) =>
182
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
183
+ ),
184
+ ]);
185
+
186
+ const url1 = page1.url();
187
+ const url2 = page2.url();
188
+
189
+ const user1Redirected =
190
+ url1.includes('/login') || url1.includes('/signin') || url1.includes('/auth');
191
+ const user2Redirected =
192
+ url2.includes('/login') || url2.includes('/signin') || url2.includes('/auth');
193
+
194
+ if (user1Redirected || user2Redirected) {
195
+ findings.push({
196
+ id: `${this.agentId}-concurrent-session-conflict`,
197
+ type: 'test-bug',
198
+ severity: 'high',
199
+ agentId: this.agentId,
200
+ module: 'multi-user',
201
+ description: `Concurrent sessions not supported — user${user1Redirected ? '1' : '2'} was redirected to login`,
202
+ });
203
+ }
204
+ } catch (error) {
205
+ findings.push({
206
+ id: `${this.agentId}-concurrent-error`,
207
+ type: 'test-bug',
208
+ severity: 'medium',
209
+ agentId: this.agentId,
210
+ module: 'multi-user',
211
+ description: `Concurrent session test failed: ${error instanceof Error ? error.message.split('\n')[0] : String(error)}`,
212
+ });
213
+ }
214
+
215
+ return findings;
216
+ }
217
+
218
+ /**
219
+ * Test 2: Both users on the same page — check both render content.
220
+ */
221
+ private async testSamePageAccess(
222
+ page1: import('playwright').Page,
223
+ page2: import('playwright').Page,
224
+ moduleUrl: string,
225
+ moduleId: string,
226
+ ): Promise<Finding[]> {
227
+ const findings: Finding[] = [];
228
+
229
+ try {
230
+ const body1 = await Promise.race([
231
+ page1.locator('body').innerText(),
232
+ new Promise<string>((_, reject) =>
233
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
234
+ ),
235
+ ]) as string;
236
+
237
+ const body2 = await Promise.race([
238
+ page2.locator('body').innerText(),
239
+ new Promise<string>((_, reject) =>
240
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
241
+ ),
242
+ ]) as string;
243
+
244
+ if (body1.trim().length === 0 || body2.trim().length === 0) {
245
+ findings.push({
246
+ id: `${this.agentId}-same-page-blank`,
247
+ type: 'test-bug',
248
+ severity: 'medium',
249
+ agentId: this.agentId,
250
+ module: moduleId,
251
+ description: `Same page access test: one or both users see a blank page for module ${moduleId}`,
252
+ });
253
+ }
254
+ } catch {
255
+ // Timeout — non-critical
256
+ }
257
+
258
+ return findings;
259
+ }
260
+
261
+ /**
262
+ * Test 3: User1 navigates to module A, User2 to module B.
263
+ * Verify each is on the correct page.
264
+ */
265
+ private async testIndependentNavigation(
266
+ page1: import('playwright').Page,
267
+ page2: import('playwright').Page,
268
+ mod1Url: string,
269
+ mod2Url: string,
270
+ mod1Id: string,
271
+ mod2Id: string,
272
+ ): Promise<Finding[]> {
273
+ const findings: Finding[] = [];
274
+
275
+ try {
276
+ await Promise.race([
277
+ Promise.all([
278
+ page1.goto(mod1Url, { waitUntil: 'domcontentloaded' }),
279
+ page2.goto(mod2Url, { waitUntil: 'domcontentloaded' }),
280
+ ]),
281
+ new Promise((_, reject) =>
282
+ setTimeout(() => reject(new Error('Timeout')), OP_TIMEOUT_MS),
283
+ ),
284
+ ]);
285
+
286
+ const url1 = page1.url();
287
+ const url2 = page2.url();
288
+
289
+ // Extract path from URLs for comparison
290
+ const path1 = new URL(url1).pathname;
291
+ const path2 = new URL(url2).pathname;
292
+ const expectedPath1 = new URL(mod1Url).pathname;
293
+ const expectedPath2 = new URL(mod2Url).pathname;
294
+
295
+ // Check if sessions cross-contaminated
296
+ if (path1 === path2 && expectedPath1 !== expectedPath2) {
297
+ findings.push({
298
+ id: `${this.agentId}-session-isolation-failure`,
299
+ type: 'code-bug-security',
300
+ severity: 'critical',
301
+ agentId: this.agentId,
302
+ module: 'multi-user',
303
+ description: `Session isolation failure — both users ended up on the same path (${path1}) despite navigating to different modules`,
304
+ });
305
+ }
306
+ } catch (navError) {
307
+ findings.push({
308
+ id: `${this.agentId}-independent-nav-error`,
309
+ type: 'test-bug',
310
+ severity: 'medium',
311
+ agentId: this.agentId,
312
+ module: 'multi-user',
313
+ description: `Independent navigation test failed: ${navError instanceof Error ? navError.message.split('\n')[0] : String(navError)}`,
314
+ });
315
+ }
316
+
317
+ return findings;
318
+ }
319
+ }