@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,164 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [
10
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
11
+ { id: 'tasks', route: '/tasks', sidebarIcon: true },
12
+ ],
13
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
14
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
15
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
16
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
17
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
18
+ agentClassification: { blocking: [], advisory: [] },
19
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
20
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ const fetchMock = vi.fn();
26
+ vi.stubGlobal('fetch', fetchMock);
27
+
28
+ const loginMock = vi.fn().mockResolvedValue(undefined);
29
+ vi.mock('../../helpers/navigation', () => ({
30
+ login: (...args: unknown[]) => loginMock(...args),
31
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
32
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
33
+ switchPortal: vi.fn().mockResolvedValue(undefined),
34
+ }));
35
+
36
+ function makeMockPage() {
37
+ return {
38
+ close: vi.fn().mockResolvedValue(undefined),
39
+ goto: vi.fn().mockResolvedValue(undefined),
40
+ evaluate: vi.fn().mockResolvedValue(false),
41
+ locator: vi.fn().mockReturnValue({ count: vi.fn().mockResolvedValue(0), first: vi.fn() }),
42
+ route: vi.fn().mockResolvedValue(undefined),
43
+ unroute: vi.fn().mockResolvedValue(undefined),
44
+ on: vi.fn(),
45
+ };
46
+ }
47
+
48
+ let currentMockPage: ReturnType<typeof makeMockPage>;
49
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
50
+
51
+ vi.mock('playwright', async () => ({
52
+ chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
53
+ }));
54
+
55
+ const { OfflineModeTesterAgent } = await import('../52-offline-mode-tester');
56
+
57
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
58
+ return new OfflineModeTesterAgent(config, phase, '/tmp/test-run');
59
+ }
60
+
61
+ describe('OfflineModeTesterAgent', () => {
62
+ beforeEach(() => {
63
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
64
+ loginMock.mockReset().mockResolvedValue(undefined);
65
+ currentMockPage = makeMockPage();
66
+ mockBrowser = {
67
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
68
+ close: vi.fn().mockResolvedValue(undefined),
69
+ };
70
+ });
71
+
72
+ it('has agentId 52 and correct name', () => {
73
+ const agent = createAgent();
74
+ expect(agent.agentId).toBe(52);
75
+ expect(agent.agentName).toBe('Offline Mode Tester');
76
+ });
77
+
78
+ it('throws when modules empty', async () => {
79
+ const agent = createAgent(makeConfig({ modules: [] }));
80
+ const result = await agent.run();
81
+ expect(result.status).toBe('failed');
82
+ });
83
+
84
+ it('reports no service worker when not detected', async () => {
85
+ currentMockPage.evaluate.mockResolvedValue(false);
86
+ const agent = createAgent();
87
+ const result = await agent.run();
88
+ const finding = result.findings.find(f => f.id.includes('no-service-worker'));
89
+ expect(finding).toBeDefined();
90
+ expect(finding!.severity).toBe('low');
91
+ });
92
+
93
+ it('reports blank page when offline', async () => {
94
+ let callCount = 0;
95
+ currentMockPage.evaluate.mockImplementation(async () => {
96
+ callCount++;
97
+ if (callCount === 1) return false; // no service worker
98
+ return ''; // blank page offline
99
+ });
100
+ const agent = createAgent();
101
+ const result = await agent.run();
102
+ const finding = result.findings.find(f => f.id.includes('blank-offline'));
103
+ expect(finding).toBeDefined();
104
+ expect(finding!.severity).toBe('medium');
105
+ });
106
+
107
+ it('reports offline message when detected', async () => {
108
+ let callCount = 0;
109
+ currentMockPage.evaluate.mockImplementation(async () => {
110
+ callCount++;
111
+ if (callCount === 1) return true; // has service worker
112
+ return 'You are offline'; // offline message
113
+ });
114
+ const agent = createAgent();
115
+ const result = await agent.run();
116
+ const finding = result.findings.find(f => f.id.includes('offline-message'));
117
+ expect(finding).toBeDefined();
118
+ });
119
+
120
+ it('handles nav failure', async () => {
121
+ currentMockPage.goto.mockRejectedValue(new Error('Nav failed'));
122
+ const agent = createAgent();
123
+ const result = await agent.run();
124
+ const finding = result.findings.find(f => f.id.includes('nav-failed'));
125
+ expect(finding).toBeDefined();
126
+ });
127
+
128
+ it('handles login failure', async () => {
129
+ loginMock.mockRejectedValue(new Error('Login failed'));
130
+ const agent = createAgent();
131
+ const result = await agent.run();
132
+ const finding = result.findings.find(f => f.id.includes('login-failed'));
133
+ expect(finding).toBeDefined();
134
+ });
135
+
136
+ it('handles playwright crash', async () => {
137
+ mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
138
+ const agent = createAgent();
139
+ const result = await agent.run();
140
+ const finding = result.findings.find(f => f.id.includes('playwright-failure'));
141
+ expect(finding).toBeDefined();
142
+ });
143
+
144
+ it('closes browser in finally', async () => {
145
+ mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
146
+ const agent = createAgent();
147
+ await agent.run();
148
+ expect(mockBrowser.close).toHaveBeenCalled();
149
+ });
150
+
151
+ it('calls route to simulate offline', async () => {
152
+ currentMockPage.evaluate.mockResolvedValue(false);
153
+ const agent = createAgent();
154
+ await agent.run();
155
+ expect(currentMockPage.route).toHaveBeenCalledWith('**/*', expect.any(Function));
156
+ });
157
+
158
+ it('handles evaluate failure for service worker check', async () => {
159
+ currentMockPage.evaluate.mockRejectedValue(new Error('Eval failed'));
160
+ const agent = createAgent();
161
+ const result = await agent.run();
162
+ expect(result.status).toBeDefined();
163
+ });
164
+ });
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
10
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
11
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
12
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
13
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
14
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
15
+ agentClassification: { blocking: [], advisory: [] },
16
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
17
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ const fetchMock = vi.fn();
23
+ vi.stubGlobal('fetch', fetchMock);
24
+
25
+ vi.mock('../../helpers/navigation', () => ({
26
+ login: vi.fn().mockResolvedValue(undefined),
27
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
28
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
29
+ switchPortal: vi.fn().mockResolvedValue(undefined),
30
+ }));
31
+
32
+ function makeMockPage() {
33
+ return {
34
+ close: vi.fn().mockResolvedValue(undefined),
35
+ goto: vi.fn().mockResolvedValue(undefined),
36
+ evaluate: vi.fn().mockResolvedValue(''),
37
+ locator: vi.fn().mockReturnValue({ count: vi.fn().mockResolvedValue(0) }),
38
+ route: vi.fn().mockResolvedValue(undefined),
39
+ on: vi.fn(),
40
+ };
41
+ }
42
+
43
+ let mockPage1: ReturnType<typeof makeMockPage>;
44
+ let mockPage2: ReturnType<typeof makeMockPage>;
45
+ let mockContext1: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
46
+ let mockContext2: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
47
+ let contextCallCount: number;
48
+ let mockBrowser: { newContext: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
49
+
50
+ vi.mock('playwright', async () => ({
51
+ chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
52
+ }));
53
+
54
+ const { GracefulDegradationTesterAgent } = await import('../53-graceful-degradation-tester');
55
+
56
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
57
+ return new GracefulDegradationTesterAgent(config, phase, '/tmp/test-run');
58
+ }
59
+
60
+ describe('GracefulDegradationTesterAgent', () => {
61
+ beforeEach(() => {
62
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
63
+ mockPage1 = makeMockPage();
64
+ mockPage2 = makeMockPage();
65
+ contextCallCount = 0;
66
+ mockContext1 = { newPage: vi.fn().mockResolvedValue(mockPage1), close: vi.fn().mockResolvedValue(undefined) };
67
+ mockContext2 = { newPage: vi.fn().mockResolvedValue(mockPage2), close: vi.fn().mockResolvedValue(undefined) };
68
+ mockBrowser = {
69
+ newContext: vi.fn().mockImplementation(async () => {
70
+ contextCallCount++;
71
+ return contextCallCount === 1 ? mockContext1 : mockContext2;
72
+ }),
73
+ close: vi.fn().mockResolvedValue(undefined),
74
+ };
75
+ });
76
+
77
+ it('has agentId 53 and correct name', () => {
78
+ const agent = createAgent();
79
+ expect(agent.agentId).toBe(53);
80
+ expect(agent.agentName).toBe('Graceful Degradation Tester');
81
+ });
82
+
83
+ it('throws when modules empty', async () => {
84
+ const agent = createAgent(makeConfig({ modules: [] }));
85
+ const result = await agent.run();
86
+ expect(result.status).toBe('failed');
87
+ });
88
+
89
+ it('reports no SSR when blank without JS', async () => {
90
+ mockPage1.evaluate.mockResolvedValue('');
91
+ mockPage2.evaluate.mockResolvedValue(0);
92
+ const agent = createAgent();
93
+ const result = await agent.run();
94
+ const finding = result.findings.find(f => f.id.includes('no-ssr'));
95
+ expect(finding).toBeDefined();
96
+ expect(finding!.severity).toBe('medium');
97
+ });
98
+
99
+ it('reports missing alt text', async () => {
100
+ mockPage1.evaluate.mockResolvedValue('Some SSR content');
101
+ mockPage2.evaluate.mockResolvedValue(3); // 3 images missing alt
102
+ const agent = createAgent();
103
+ const result = await agent.run();
104
+ const finding = result.findings.find(f => f.id.includes('missing-alt'));
105
+ expect(finding).toBeDefined();
106
+ expect(finding!.severity).toBe('medium');
107
+ });
108
+
109
+ it('no finding when SSR works and alt text present', async () => {
110
+ mockPage1.evaluate.mockResolvedValue('Server rendered content');
111
+ mockPage2.evaluate.mockResolvedValue(0); // no missing alt
112
+ const agent = createAgent();
113
+ const result = await agent.run();
114
+ const ssrFinding = result.findings.find(f => f.id.includes('no-ssr'));
115
+ const altFinding = result.findings.find(f => f.id.includes('missing-alt'));
116
+ expect(ssrFinding).toBeUndefined();
117
+ expect(altFinding).toBeUndefined();
118
+ });
119
+
120
+ it('handles playwright crash', async () => {
121
+ mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
122
+ const agent = createAgent();
123
+ const result = await agent.run();
124
+ const finding = result.findings.find(f => f.id.includes('playwright-failure'));
125
+ expect(finding).toBeDefined();
126
+ });
127
+
128
+ it('closes browser in finally', async () => {
129
+ mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
130
+ const agent = createAgent();
131
+ await agent.run();
132
+ expect(mockBrowser.close).toHaveBeenCalled();
133
+ });
134
+
135
+ it('handles no-JS navigation failure', async () => {
136
+ mockPage1.goto.mockRejectedValue(new Error('Page failed'));
137
+ mockPage2.evaluate.mockResolvedValue(0);
138
+ const agent = createAgent();
139
+ const result = await agent.run();
140
+ const finding = result.findings.find(f => f.id.includes('no-js-nav-failed'));
141
+ expect(finding).toBeDefined();
142
+ });
143
+
144
+ it('handles image test failure', async () => {
145
+ mockPage1.evaluate.mockResolvedValue('Content');
146
+ mockPage2.goto.mockRejectedValue(new Error('Nav failed'));
147
+ const agent = createAgent();
148
+ const result = await agent.run();
149
+ const finding = result.findings.find(f => f.id.includes('img-test-failed'));
150
+ expect(finding).toBeDefined();
151
+ });
152
+
153
+ it('blocks image routes in test 2', async () => {
154
+ mockPage1.evaluate.mockResolvedValue('Content');
155
+ mockPage2.evaluate.mockResolvedValue(0);
156
+ const agent = createAgent();
157
+ await agent.run();
158
+ expect(mockPage2.route).toHaveBeenCalledWith('**/*.{png,jpg,jpeg,gif,webp,svg,ico}', expect.any(Function));
159
+ });
160
+
161
+ it('creates context with javaScriptEnabled false for test 1', async () => {
162
+ mockPage1.evaluate.mockResolvedValue('Content');
163
+ mockPage2.evaluate.mockResolvedValue(0);
164
+ const agent = createAgent();
165
+ await agent.run();
166
+ expect(mockBrowser.newContext).toHaveBeenCalledWith({ javaScriptEnabled: false });
167
+ });
168
+ });
@@ -0,0 +1,157 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
10
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
11
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
12
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
13
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
14
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
15
+ agentClassification: { blocking: [], advisory: [] },
16
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
17
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ const fetchMock = vi.fn();
23
+ vi.stubGlobal('fetch', fetchMock);
24
+
25
+ const loginMock = vi.fn().mockResolvedValue(undefined);
26
+ vi.mock('../../helpers/navigation', () => ({
27
+ login: (...args: unknown[]) => loginMock(...args),
28
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
29
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
30
+ switchPortal: vi.fn().mockResolvedValue(undefined),
31
+ }));
32
+
33
+ function makeMockPage() {
34
+ return {
35
+ close: vi.fn().mockResolvedValue(undefined),
36
+ goto: vi.fn().mockResolvedValue(undefined),
37
+ on: vi.fn(),
38
+ locator: vi.fn().mockReturnValue({ count: vi.fn().mockResolvedValue(0) }),
39
+ };
40
+ }
41
+
42
+ let currentMockPage: ReturnType<typeof makeMockPage>;
43
+ let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
44
+
45
+ vi.mock('playwright', async () => ({
46
+ chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
47
+ }));
48
+
49
+ const { WebSocketTesterAgent } = await import('../54-websocket-tester');
50
+
51
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
52
+ return new WebSocketTesterAgent(config, phase, '/tmp/test-run');
53
+ }
54
+
55
+ describe('WebSocketTesterAgent', () => {
56
+ beforeEach(() => {
57
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
58
+ loginMock.mockReset().mockResolvedValue(undefined);
59
+ currentMockPage = makeMockPage();
60
+ mockBrowser = {
61
+ newPage: vi.fn().mockResolvedValue(currentMockPage),
62
+ close: vi.fn().mockResolvedValue(undefined),
63
+ };
64
+ });
65
+
66
+ it('has agentId 54 and correct name', () => {
67
+ const agent = createAgent();
68
+ expect(agent.agentId).toBe(54);
69
+ expect(agent.agentName).toBe('WebSocket Tester');
70
+ });
71
+
72
+ it('throws when modules empty', async () => {
73
+ const agent = createAgent(makeConfig({ modules: [] }));
74
+ const result = await agent.run();
75
+ expect(result.status).toBe('failed');
76
+ });
77
+
78
+ it('reports no WS connections when none detected', async () => {
79
+ const agent = createAgent();
80
+ const result = await agent.run();
81
+ const finding = result.findings.find(f => f.id.includes('no-ws-connections'));
82
+ expect(finding).toBeDefined();
83
+ expect(finding!.severity).toBe('low');
84
+ });
85
+
86
+ it('reports WS configured but no connection', async () => {
87
+ const config = makeConfig({ integrations: [{ name: 'websocket' }] });
88
+ const agent = createAgent(config);
89
+ const result = await agent.run();
90
+ const finding = result.findings.find(f => f.id.includes('ws-configured-no-connection'));
91
+ expect(finding).toBeDefined();
92
+ expect(finding!.severity).toBe('medium');
93
+ });
94
+
95
+ it('registers websocket event listener', async () => {
96
+ const agent = createAgent();
97
+ await agent.run();
98
+ expect(currentMockPage.on).toHaveBeenCalledWith('websocket', expect.any(Function));
99
+ });
100
+
101
+ it('handles login failure', async () => {
102
+ loginMock.mockRejectedValue(new Error('Login failed'));
103
+ const agent = createAgent();
104
+ const result = await agent.run();
105
+ const finding = result.findings.find(f => f.id.includes('login-failed'));
106
+ expect(finding).toBeDefined();
107
+ });
108
+
109
+ it('handles playwright crash', async () => {
110
+ mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
111
+ const agent = createAgent();
112
+ const result = await agent.run();
113
+ const finding = result.findings.find(f => f.id.includes('playwright-failure'));
114
+ expect(finding).toBeDefined();
115
+ });
116
+
117
+ it('closes browser in finally', async () => {
118
+ mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
119
+ const agent = createAgent();
120
+ await agent.run();
121
+ expect(mockBrowser.close).toHaveBeenCalled();
122
+ });
123
+
124
+ it('detects WS errors from event handler', async () => {
125
+ currentMockPage.on.mockImplementation((event: string, handler: Function) => {
126
+ if (event === 'websocket') {
127
+ const mockWs = {
128
+ url: () => 'ws://localhost:3000/ws',
129
+ on: (evt: string, cb: Function) => {
130
+ if (evt === 'socketerror') cb('Error');
131
+ },
132
+ };
133
+ handler(mockWs);
134
+ }
135
+ });
136
+ const agent = createAgent();
137
+ const result = await agent.run();
138
+ const finding = result.findings.find(f => f.id.includes('ws-error'));
139
+ expect(finding).toBeDefined();
140
+ expect(finding!.severity).toBe('medium');
141
+ });
142
+
143
+ it('no credentials continues', async () => {
144
+ const config = makeConfig({ auth: { loginUrl: '/login', credentials: {} } });
145
+ const agent = createAgent(config);
146
+ const result = await agent.run();
147
+ expect(result.status).toBeDefined();
148
+ });
149
+
150
+ it('handles nav failure for module', async () => {
151
+ currentMockPage.goto.mockRejectedValue(new Error('Nav failed'));
152
+ const agent = createAgent();
153
+ const result = await agent.run();
154
+ // Should not crash, just skip module
155
+ expect(result.status).toBeDefined();
156
+ });
157
+ });
@@ -0,0 +1,181 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [
10
+ { id: 'dashboard', route: '/dashboard', sidebarIcon: true },
11
+ { id: 'tasks', route: '/tasks', sidebarIcon: true },
12
+ ],
13
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
14
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
15
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
16
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
17
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
18
+ agentClassification: { blocking: [], advisory: [] },
19
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
20
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ const fetchMock = vi.fn();
26
+ vi.stubGlobal('fetch', fetchMock);
27
+
28
+ const loginMock = vi.fn().mockResolvedValue(undefined);
29
+ vi.mock('../../helpers/navigation', () => ({
30
+ login: (...args: unknown[]) => loginMock(...args),
31
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
32
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
33
+ switchPortal: vi.fn().mockResolvedValue(undefined),
34
+ }));
35
+
36
+ function makeMockLocator(countVal = 0) {
37
+ const loc: Record<string, ReturnType<typeof vi.fn>> = {
38
+ count: vi.fn().mockResolvedValue(countVal),
39
+ first: vi.fn(),
40
+ nth: vi.fn(),
41
+ fill: vi.fn().mockResolvedValue(undefined),
42
+ click: vi.fn().mockResolvedValue(undefined),
43
+ };
44
+ loc.first.mockReturnValue(loc);
45
+ loc.nth.mockReturnValue(loc);
46
+ return loc;
47
+ }
48
+
49
+ function makeMockPage() {
50
+ return {
51
+ close: vi.fn().mockResolvedValue(undefined),
52
+ goto: vi.fn().mockResolvedValue(undefined),
53
+ evaluate: vi.fn().mockResolvedValue(5),
54
+ locator: vi.fn().mockReturnValue(makeMockLocator(0)),
55
+ on: vi.fn(),
56
+ };
57
+ }
58
+
59
+ let mockPage1: ReturnType<typeof makeMockPage>;
60
+ let mockPage2: ReturnType<typeof makeMockPage>;
61
+ let mockContext1: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
62
+ let mockContext2: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
63
+ let contextCallCount: number;
64
+ let mockBrowser: { newContext: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
65
+
66
+ vi.mock('playwright', async () => ({
67
+ chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
68
+ }));
69
+
70
+ const { RealtimeSyncTesterAgent } = await import('../55-realtime-sync-tester');
71
+
72
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
73
+ return new RealtimeSyncTesterAgent(config, phase, '/tmp/test-run');
74
+ }
75
+
76
+ describe('RealtimeSyncTesterAgent', () => {
77
+ beforeEach(() => {
78
+ fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
79
+ loginMock.mockReset().mockResolvedValue(undefined);
80
+ mockPage1 = makeMockPage();
81
+ mockPage2 = makeMockPage();
82
+ contextCallCount = 0;
83
+ mockContext1 = { newPage: vi.fn().mockResolvedValue(mockPage1), close: vi.fn().mockResolvedValue(undefined) };
84
+ mockContext2 = { newPage: vi.fn().mockResolvedValue(mockPage2), close: vi.fn().mockResolvedValue(undefined) };
85
+ mockBrowser = {
86
+ newContext: vi.fn().mockImplementation(async () => {
87
+ contextCallCount++;
88
+ return contextCallCount === 1 ? mockContext1 : mockContext2;
89
+ }),
90
+ close: vi.fn().mockResolvedValue(undefined),
91
+ };
92
+ });
93
+
94
+ it('has agentId 55 and correct name', () => {
95
+ const agent = createAgent();
96
+ expect(agent.agentId).toBe(55);
97
+ expect(agent.agentName).toBe('Realtime Sync Tester');
98
+ });
99
+
100
+ it('throws when modules empty', async () => {
101
+ const agent = createAgent(makeConfig({ modules: [] }));
102
+ const result = await agent.run();
103
+ expect(result.status).toBe('failed');
104
+ });
105
+
106
+ it('reports no credentials when none configured', async () => {
107
+ const config = makeConfig({ auth: { loginUrl: '/login', credentials: {} } });
108
+ const agent = createAgent(config);
109
+ const result = await agent.run();
110
+ const finding = result.findings.find(f => f.id.includes('no-credentials'));
111
+ expect(finding).toBeDefined();
112
+ });
113
+
114
+ it('reports no create button when none found', async () => {
115
+ mockPage1.locator.mockReturnValue(makeMockLocator(0));
116
+ const agent = createAgent();
117
+ const result = await agent.run();
118
+ const finding = result.findings.find(f => f.id.includes('no-create-button'));
119
+ expect(finding).toBeDefined();
120
+ });
121
+
122
+ it('reports no realtime sync when item not visible', async () => {
123
+ mockPage1.locator.mockReturnValue(makeMockLocator(1));
124
+ mockPage1.evaluate.mockResolvedValue(5);
125
+ mockPage2.evaluate.mockResolvedValue(5); // same count — no sync
126
+ const agent = createAgent();
127
+ const result = await agent.run();
128
+ const finding = result.findings.find(f => f.id.includes('no-realtime-sync'));
129
+ expect(finding).toBeDefined();
130
+ expect(finding!.severity).toBe('low');
131
+ });
132
+
133
+ it('handles login failure', async () => {
134
+ loginMock.mockRejectedValue(new Error('Login failed'));
135
+ const agent = createAgent();
136
+ const result = await agent.run();
137
+ const finding = result.findings.find(f => f.id.includes('login-failed'));
138
+ expect(finding).toBeDefined();
139
+ });
140
+
141
+ it('handles nav failure', async () => {
142
+ mockPage1.goto.mockRejectedValue(new Error('Nav failed'));
143
+ const agent = createAgent();
144
+ const result = await agent.run();
145
+ const finding = result.findings.find(f => f.id.includes('nav-failed'));
146
+ expect(finding).toBeDefined();
147
+ });
148
+
149
+ it('handles playwright crash', async () => {
150
+ mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
151
+ const agent = createAgent();
152
+ const result = await agent.run();
153
+ const finding = result.findings.find(f => f.id.includes('playwright-failure'));
154
+ expect(finding).toBeDefined();
155
+ });
156
+
157
+ it('closes browser in finally', async () => {
158
+ mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
159
+ const agent = createAgent();
160
+ await agent.run();
161
+ expect(mockBrowser.close).toHaveBeenCalled();
162
+ });
163
+
164
+ it('handles create action failure', async () => {
165
+ const createLocator = makeMockLocator(1);
166
+ createLocator.first.mockReturnValue({
167
+ click: vi.fn().mockRejectedValue(new Error('Click failed')),
168
+ });
169
+ mockPage1.locator.mockReturnValue(createLocator);
170
+ const agent = createAgent();
171
+ const result = await agent.run();
172
+ const finding = result.findings.find(f => f.id.includes('create-failed'));
173
+ expect(finding).toBeDefined();
174
+ });
175
+
176
+ it('creates two browser contexts', async () => {
177
+ const agent = createAgent();
178
+ await agent.run();
179
+ expect(mockBrowser.newContext).toHaveBeenCalledTimes(2);
180
+ });
181
+ });