@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,213 @@
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
+ const NAV_TIMEOUT_MS = 10_000;
8
+
9
+ export class LighthouseAuditorAgent extends BaseAgent {
10
+ readonly agentId = 48;
11
+ readonly agentName = 'Lighthouse Auditor';
12
+ private baseUrl = '';
13
+
14
+ protected async preFlight(): Promise<void> {
15
+ if (!this.config.modules || this.config.modules.length === 0) {
16
+ throw new Error('LighthouseAuditorAgent requires at least one module in config.modules');
17
+ }
18
+ const { env } = await resolveEnvironment(this.config, this.phase);
19
+ this.baseUrl = env.baseUrl;
20
+ }
21
+
22
+ protected async execute(): Promise<Finding[]> {
23
+ const findings: Finding[] = [];
24
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
25
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
26
+ const credentials =
27
+ this.config.auth.credentials?.['admin'] ??
28
+ Object.values(this.config.auth.credentials ?? {})[0];
29
+
30
+ const { chromium } = await import('playwright');
31
+ let browser: Browser | null = null;
32
+
33
+ try {
34
+ browser = await chromium.launch({ headless: true });
35
+ const page = await browser.newPage();
36
+
37
+ if (credentials) {
38
+ try {
39
+ await login(page, credentials, loginUrl);
40
+ } catch (loginError) {
41
+ findings.push({
42
+ id: `${this.agentId}-login-failed`,
43
+ type: 'infra-issue',
44
+ severity: 'medium',
45
+ agentId: this.agentId,
46
+ module: 'lighthouse-auditor',
47
+ description: `Login failed — continuing anonymously: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
48
+ });
49
+ }
50
+ }
51
+
52
+ for (const mod of this.config.modules) {
53
+ const moduleFindings = await this.auditModule(page, mod.id, mod.route);
54
+ findings.push(...moduleFindings);
55
+ }
56
+
57
+ await page.close().catch(() => undefined);
58
+ } catch (playwrightError) {
59
+ findings.push({
60
+ id: `${this.agentId}-playwright-failure`,
61
+ type: 'infra-issue',
62
+ severity: 'medium',
63
+ agentId: this.agentId,
64
+ module: 'lighthouse-auditor',
65
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
66
+ });
67
+ } finally {
68
+ if (browser) await browser.close().catch(() => undefined);
69
+ }
70
+
71
+ return findings;
72
+ }
73
+
74
+ private async auditModule(
75
+ page: Page,
76
+ moduleId: string,
77
+ moduleRoute: string,
78
+ ): Promise<Finding[]> {
79
+ const findings: Finding[] = [];
80
+ const fullUrl = moduleRoute.startsWith('http')
81
+ ? moduleRoute
82
+ : `${this.baseUrl}${moduleRoute}`;
83
+
84
+ try {
85
+ await Promise.race([
86
+ page.goto(fullUrl, { waitUntil: 'domcontentloaded' }),
87
+ new Promise((_, reject) =>
88
+ setTimeout(() => reject(new Error('Navigation timeout')), NAV_TIMEOUT_MS),
89
+ ),
90
+ ]);
91
+ } catch {
92
+ findings.push({
93
+ id: `${this.agentId}-nav-failed-${moduleId}`,
94
+ type: 'test-bug',
95
+ severity: 'medium',
96
+ agentId: this.agentId,
97
+ module: moduleId,
98
+ description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
99
+ });
100
+ return findings;
101
+ }
102
+
103
+ // Measure performance timing
104
+ try {
105
+ const metrics = await page.evaluate(() => {
106
+ const timing = performance.timing;
107
+ const navigationStart = timing.navigationStart;
108
+ const entries = performance.getEntriesByType('paint') as PerformancePaintTiming[];
109
+ const fcp = entries.find(e => e.name === 'first-contentful-paint');
110
+
111
+ return {
112
+ domContentLoaded: timing.domContentLoadedEventEnd - navigationStart,
113
+ loadComplete: timing.loadEventEnd - navigationStart,
114
+ fcp: fcp?.startTime ?? null,
115
+ };
116
+ });
117
+
118
+ const fcpThreshold = this.config.thresholds?.lighthouse?.performance
119
+ ? (100 - this.config.thresholds.lighthouse.performance) * 50 + 1000
120
+ : 2500;
121
+
122
+ if (metrics.fcp !== null && metrics.fcp > fcpThreshold) {
123
+ findings.push({
124
+ id: `${this.agentId}-slow-fcp-${moduleId}`,
125
+ type: 'test-bug',
126
+ severity: 'medium',
127
+ agentId: this.agentId,
128
+ module: moduleId,
129
+ description: `Module ${moduleId} First Contentful Paint is ${metrics.fcp.toFixed(0)}ms (threshold: ${fcpThreshold}ms)`,
130
+ });
131
+ }
132
+
133
+ if (metrics.domContentLoaded > 3000) {
134
+ findings.push({
135
+ id: `${this.agentId}-slow-dcl-${moduleId}`,
136
+ type: 'test-bug',
137
+ severity: 'medium',
138
+ agentId: this.agentId,
139
+ module: moduleId,
140
+ description: `Module ${moduleId} DOMContentLoaded took ${metrics.domContentLoaded}ms`,
141
+ });
142
+ }
143
+
144
+ this.addEvidence({
145
+ type: 'log',
146
+ path: `perf-${moduleId}`,
147
+ description: `Module ${moduleId}: FCP=${metrics.fcp?.toFixed(0) ?? 'N/A'}ms, DCL=${metrics.domContentLoaded}ms, Load=${metrics.loadComplete}ms`,
148
+ });
149
+ } catch {
150
+ // Performance metrics unavailable
151
+ }
152
+
153
+ // Check <meta name="viewport">
154
+ try {
155
+ const hasViewport = await page.evaluate(() => {
156
+ return document.querySelector('meta[name="viewport"]') !== null;
157
+ });
158
+ if (!hasViewport) {
159
+ findings.push({
160
+ id: `${this.agentId}-no-viewport-${moduleId}`,
161
+ type: 'test-bug',
162
+ severity: 'medium',
163
+ agentId: this.agentId,
164
+ module: moduleId,
165
+ description: `Module ${moduleId} is missing <meta name="viewport">`,
166
+ });
167
+ }
168
+ } catch {
169
+ // Could not evaluate viewport
170
+ }
171
+
172
+ // Check <html lang>
173
+ try {
174
+ const hasLang = await page.evaluate(() => {
175
+ return document.documentElement.hasAttribute('lang');
176
+ });
177
+ if (!hasLang) {
178
+ findings.push({
179
+ id: `${this.agentId}-no-lang-${moduleId}`,
180
+ type: 'test-bug',
181
+ severity: 'medium',
182
+ agentId: this.agentId,
183
+ module: moduleId,
184
+ description: `Module ${moduleId} is missing lang attribute on <html>`,
185
+ });
186
+ }
187
+ } catch {
188
+ // Could not evaluate lang
189
+ }
190
+
191
+ // Check <meta charset>
192
+ try {
193
+ const hasCharset = await page.evaluate(() => {
194
+ return document.querySelector('meta[charset]') !== null ||
195
+ document.querySelector('meta[http-equiv="Content-Type"]') !== null;
196
+ });
197
+ if (!hasCharset) {
198
+ findings.push({
199
+ id: `${this.agentId}-no-charset-${moduleId}`,
200
+ type: 'test-bug',
201
+ severity: 'low',
202
+ agentId: this.agentId,
203
+ module: moduleId,
204
+ description: `Module ${moduleId} is missing <meta charset>`,
205
+ });
206
+ }
207
+ } catch {
208
+ // Could not evaluate charset
209
+ }
210
+
211
+ return findings;
212
+ }
213
+ }
@@ -0,0 +1,198 @@
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
+ /** Pattern that detects raw i18n keys like home.title, nav.dashboard.items */
8
+ const I18N_KEY_PATTERN = /\b[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*/;
9
+
10
+ /** Selectors for language switcher UI elements. */
11
+ const LANG_SWITCHER_SELECTORS = [
12
+ '[data-testid*="lang"]',
13
+ '[data-testid*="locale"]',
14
+ '[data-testid*="language"]',
15
+ 'select[name*="lang"]',
16
+ 'select[name*="locale"]',
17
+ 'button:has-text("English")',
18
+ 'button:has-text("EN")',
19
+ '[aria-label*="language"]',
20
+ '[aria-label*="locale"]',
21
+ ] as const;
22
+
23
+ /** RTL locales to check. */
24
+ const RTL_LOCALES = ['ar', 'he', 'fa', 'ur'] as const;
25
+
26
+ export class I18nTesterAgent extends BaseAgent {
27
+ readonly agentId = 49;
28
+ readonly agentName = 'i18n Tester';
29
+ private baseUrl = '';
30
+
31
+ protected async preFlight(): Promise<void> {
32
+ if (!this.config.modules || this.config.modules.length === 0) {
33
+ throw new Error('I18nTesterAgent requires at least one module in config.modules');
34
+ }
35
+
36
+ const { env } = await resolveEnvironment(this.config, this.phase);
37
+ this.baseUrl = env.baseUrl;
38
+ }
39
+
40
+ protected async execute(): Promise<Finding[]> {
41
+ const findings: Finding[] = [];
42
+
43
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
44
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
45
+ const credentials =
46
+ this.config.auth.credentials?.['admin'] ??
47
+ Object.values(this.config.auth.credentials ?? {})[0];
48
+
49
+ const { chromium } = await import('playwright');
50
+
51
+ let browser: Browser | null = null;
52
+
53
+ try {
54
+ browser = await chromium.launch({ headless: true });
55
+ const page = await browser.newPage();
56
+
57
+ if (credentials) {
58
+ try {
59
+ await login(page, credentials, loginUrl);
60
+ } catch {
61
+ findings.push({
62
+ id: `${this.agentId}-login-failed`,
63
+ type: 'infra-issue',
64
+ severity: 'medium',
65
+ agentId: this.agentId,
66
+ module: 'i18n-tester',
67
+ description: 'Login failed — continuing anonymously',
68
+ });
69
+ }
70
+ }
71
+
72
+ // Check for language switcher
73
+ const langSwitcherFound = await this.findLanguageSwitcher(page);
74
+
75
+ if (!langSwitcherFound) {
76
+ findings.push({
77
+ id: `${this.agentId}-no-lang-switcher`,
78
+ type: 'test-bug',
79
+ severity: 'low',
80
+ agentId: this.agentId,
81
+ module: 'i18n-tester',
82
+ description: 'No language switcher UI element found',
83
+ });
84
+ }
85
+
86
+ // Check pages for untranslated strings
87
+ for (const mod of this.config.modules) {
88
+ const moduleFindings = await this.checkModule(page, mod.id, mod.route);
89
+ findings.push(...moduleFindings);
90
+ }
91
+
92
+ // Check RTL layout if language switcher found
93
+ if (langSwitcherFound) {
94
+ const rtlFindings = await this.checkRtlLayout(page);
95
+ findings.push(...rtlFindings);
96
+ }
97
+
98
+ await page.close().catch(() => undefined);
99
+ } catch (playwrightError) {
100
+ findings.push({
101
+ id: `${this.agentId}-playwright-failure`,
102
+ type: 'infra-issue',
103
+ severity: 'medium',
104
+ agentId: this.agentId,
105
+ module: 'i18n-tester',
106
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
107
+ });
108
+ } finally {
109
+ if (browser) await browser.close().catch(() => undefined);
110
+ }
111
+
112
+ return findings;
113
+ }
114
+
115
+ private async findLanguageSwitcher(page: Page): Promise<boolean> {
116
+ const selector = LANG_SWITCHER_SELECTORS.join(', ');
117
+ try {
118
+ const count = await page.locator(selector).count();
119
+ return count > 0;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ private async checkModule(
126
+ page: Page,
127
+ moduleId: string,
128
+ moduleRoute: string,
129
+ ): Promise<Finding[]> {
130
+ const findings: Finding[] = [];
131
+ const fullUrl = moduleRoute.startsWith('http')
132
+ ? moduleRoute
133
+ : `${this.baseUrl}${moduleRoute}`;
134
+
135
+ try {
136
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
137
+ } catch {
138
+ findings.push({
139
+ id: `${this.agentId}-nav-failed-${moduleId}`,
140
+ type: 'test-bug',
141
+ severity: 'medium',
142
+ agentId: this.agentId,
143
+ module: moduleId,
144
+ description: `Failed to navigate to module ${moduleId} at ${moduleRoute}`,
145
+ });
146
+ return findings;
147
+ }
148
+
149
+ // Check for untranslated i18n keys
150
+ try {
151
+ const bodyText = await page.evaluate(() => document.body.innerText);
152
+ const lines = bodyText.split('\n');
153
+ for (const line of lines) {
154
+ if (I18N_KEY_PATTERN.test(line.trim()) && !line.includes(' ')) {
155
+ findings.push({
156
+ id: `${this.agentId}-untranslated-${moduleId}`,
157
+ type: 'test-bug',
158
+ severity: 'medium',
159
+ agentId: this.agentId,
160
+ module: moduleId,
161
+ description: `Possible untranslated i18n key found: "${line.trim().substring(0, 80)}"`,
162
+ });
163
+ break; // Report one per module
164
+ }
165
+ }
166
+ } catch {
167
+ // evaluate failed — non-critical
168
+ }
169
+
170
+ return findings;
171
+ }
172
+
173
+ private async checkRtlLayout(page: Page): Promise<Finding[]> {
174
+ const findings: Finding[] = [];
175
+
176
+ try {
177
+ const dir = await page.evaluate(() => document.dir || document.documentElement.dir);
178
+ const lang = await page.evaluate(() => document.documentElement.lang);
179
+
180
+ const isRtlLocale = RTL_LOCALES.some(locale => lang?.startsWith(locale));
181
+
182
+ if (isRtlLocale && dir !== 'rtl') {
183
+ findings.push({
184
+ id: `${this.agentId}-rtl-missing`,
185
+ type: 'test-bug',
186
+ severity: 'medium',
187
+ agentId: this.agentId,
188
+ module: 'i18n-tester',
189
+ description: `RTL locale "${lang}" detected but document.dir is "${dir ?? 'not set'}" instead of "rtl"`,
190
+ });
191
+ }
192
+ } catch {
193
+ // evaluate failed — non-critical
194
+ }
195
+
196
+ return findings;
197
+ }
198
+ }
@@ -0,0 +1,173 @@
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
+ /** Timezones to test across. */
8
+ const TEST_TIMEZONES = ['UTC', 'America/New_York', 'Australia/Sydney'] as const;
9
+
10
+ /** Selectors that commonly hold date values. */
11
+ const DATE_SELECTORS = 'time, [datetime], .date, [data-date], [data-testid*="date"]';
12
+
13
+ export class TimezoneTesterAgent extends BaseAgent {
14
+ readonly agentId = 50;
15
+ readonly agentName = 'Timezone Tester';
16
+ private baseUrl = '';
17
+
18
+ protected async preFlight(): Promise<void> {
19
+ if (!this.config.modules || this.config.modules.length === 0) {
20
+ throw new Error('TimezoneTesterAgent requires at least one module in config.modules');
21
+ }
22
+
23
+ const { env } = await resolveEnvironment(this.config, this.phase);
24
+ this.baseUrl = env.baseUrl;
25
+ }
26
+
27
+ protected async execute(): Promise<Finding[]> {
28
+ const findings: Finding[] = [];
29
+
30
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
31
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
32
+ const credentials =
33
+ this.config.auth.credentials?.['admin'] ??
34
+ Object.values(this.config.auth.credentials ?? {})[0];
35
+
36
+ const { chromium } = await import('playwright');
37
+
38
+ let browser: Browser | null = null;
39
+
40
+ try {
41
+ browser = await chromium.launch({ headless: true });
42
+
43
+ const firstModule = this.config.modules[0];
44
+ const fullUrl = firstModule.route.startsWith('http')
45
+ ? firstModule.route
46
+ : `${this.baseUrl}${firstModule.route}`;
47
+
48
+ const datesByTimezone: Record<string, string[]> = {};
49
+
50
+ for (const tz of TEST_TIMEZONES) {
51
+ const context = await browser.newContext({ timezoneId: tz });
52
+ const page = await context.newPage();
53
+
54
+ if (credentials) {
55
+ try {
56
+ await login(page, credentials, loginUrl);
57
+ } catch {
58
+ // Continue without auth
59
+ }
60
+ }
61
+
62
+ try {
63
+ await page.goto(fullUrl, { waitUntil: 'domcontentloaded', timeout: 10_000 });
64
+ } catch {
65
+ findings.push({
66
+ id: `${this.agentId}-nav-failed-${tz}`,
67
+ type: 'test-bug',
68
+ severity: 'medium',
69
+ agentId: this.agentId,
70
+ module: firstModule.id,
71
+ description: `Failed to navigate to ${firstModule.id} with timezone ${tz}`,
72
+ });
73
+ await context.close().catch(() => undefined);
74
+ continue;
75
+ }
76
+
77
+ // Read date elements
78
+ try {
79
+ const dates = await page.evaluate((selector: string) => {
80
+ const elements = document.querySelectorAll(selector);
81
+ return Array.from(elements).map(el => el.textContent?.trim() ?? '');
82
+ }, DATE_SELECTORS);
83
+
84
+ datesByTimezone[tz] = dates.filter(d => d.length > 0);
85
+ } catch {
86
+ datesByTimezone[tz] = [];
87
+ }
88
+
89
+ await page.close().catch(() => undefined);
90
+ await context.close().catch(() => undefined);
91
+ }
92
+
93
+ // Check format consistency across timezones
94
+ const timezoneKeys = Object.keys(datesByTimezone);
95
+ if (timezoneKeys.length >= 2) {
96
+ const firstTzDates = datesByTimezone[timezoneKeys[0]];
97
+ const otherTzKeys = timezoneKeys.slice(1);
98
+
99
+ for (const otherTz of otherTzKeys) {
100
+ const otherDates = datesByTimezone[otherTz];
101
+ if (firstTzDates.length !== otherDates.length) {
102
+ findings.push({
103
+ id: `${this.agentId}-date-count-mismatch-${otherTz}`,
104
+ type: 'test-bug',
105
+ severity: 'low',
106
+ agentId: this.agentId,
107
+ module: firstModule.id,
108
+ description: `Date element count differs between ${timezoneKeys[0]} (${firstTzDates.length}) and ${otherTz} (${otherDates.length})`,
109
+ });
110
+ }
111
+
112
+ // Check for day-level differences (off-by-one)
113
+ const minLen = Math.min(firstTzDates.length, otherDates.length);
114
+ for (let i = 0; i < minLen; i++) {
115
+ const dayDiff = this.detectDayDifference(firstTzDates[i], otherDates[i]);
116
+ if (dayDiff) {
117
+ findings.push({
118
+ id: `${this.agentId}-off-by-one-${otherTz}-${i}`,
119
+ type: 'test-bug',
120
+ severity: 'medium',
121
+ agentId: this.agentId,
122
+ module: firstModule.id,
123
+ description: `Potential off-by-one date: "${firstTzDates[i]}" (${timezoneKeys[0]}) vs "${otherDates[i]}" (${otherTz})`,
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ if (timezoneKeys.every(tz => datesByTimezone[tz].length === 0)) {
130
+ findings.push({
131
+ id: `${this.agentId}-no-dates-found`,
132
+ type: 'test-bug',
133
+ severity: 'low',
134
+ agentId: this.agentId,
135
+ module: firstModule.id,
136
+ description: 'No date elements found on the page to test timezone behavior',
137
+ });
138
+ }
139
+ }
140
+ } catch (playwrightError) {
141
+ findings.push({
142
+ id: `${this.agentId}-playwright-failure`,
143
+ type: 'infra-issue',
144
+ severity: 'medium',
145
+ agentId: this.agentId,
146
+ module: 'timezone-tester',
147
+ description: `Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
148
+ });
149
+ } finally {
150
+ if (browser) await browser.close().catch(() => undefined);
151
+ }
152
+
153
+ return findings;
154
+ }
155
+
156
+ private detectDayDifference(dateA: string, dateB: string): boolean {
157
+ if (dateA === dateB) return false;
158
+
159
+ // Extract day numbers from both strings
160
+ const dayPatternA = dateA.match(/\b(\d{1,2})\b/g);
161
+ const dayPatternB = dateB.match(/\b(\d{1,2})\b/g);
162
+
163
+ if (!dayPatternA || !dayPatternB) return false;
164
+
165
+ // If any day number differs by exactly 1, it could be an off-by-one
166
+ for (let i = 0; i < Math.min(dayPatternA.length, dayPatternB.length); i++) {
167
+ const diff = Math.abs(parseInt(dayPatternA[i], 10) - parseInt(dayPatternB[i], 10));
168
+ if (diff === 1) return true;
169
+ }
170
+
171
+ return false;
172
+ }
173
+ }