@avi770/testteam 1.2.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 (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. package/package.json +88 -0
@@ -0,0 +1,203 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Finding } from '../core/types';
3
+ import type { ValidatedConfig } from '../core/config';
4
+ import { BaseAgent } from './base-agent';
5
+ import { login, waitForSectionLoad } from '../helpers/navigation';
6
+ import { captureScreenshot } from '../helpers/screenshot';
7
+ import { fillForm } from '../helpers/form-filler';
8
+ import { resolveEnvironment, isUrlReachable } from '../helpers/env-resolver';
9
+
10
+ export class WorkflowCompletionTesterAgent extends BaseAgent {
11
+ readonly agentId = 20;
12
+ readonly agentName = 'Workflow Completion Tester';
13
+ private baseUrl = '';
14
+
15
+ protected async preFlight(): Promise<void> {
16
+ if (!this.config.workflows || this.config.workflows.length === 0) {
17
+ throw new Error(
18
+ 'WorkflowCompletionTesterAgent requires at least one workflow in config.workflows',
19
+ );
20
+ }
21
+
22
+ const { env } = await resolveEnvironment(this.config, this.phase);
23
+ this.baseUrl = env.baseUrl;
24
+ }
25
+
26
+ protected async execute(): Promise<Finding[]> {
27
+ const findings: Finding[] = [];
28
+
29
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
30
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
31
+ const credentials =
32
+ this.config.auth.credentials?.['admin'] ??
33
+ Object.values(this.config.auth.credentials ?? {})[0];
34
+
35
+ if (!credentials) {
36
+ findings.push({
37
+ id: `${this.agentId}-no-credentials`,
38
+ type: 'infra-issue',
39
+ severity: 'critical',
40
+ agentId: this.agentId,
41
+ module: 'workflow-completion-tester',
42
+ description: 'No credentials configured — cannot log in to run workflow tests',
43
+ });
44
+ return findings;
45
+ }
46
+
47
+ const { chromium } = await import('playwright');
48
+
49
+ let browser: Browser | null = null;
50
+ let page: Page | null = null;
51
+
52
+ try {
53
+ browser = await chromium.launch({ headless: true });
54
+ page = await browser.newPage();
55
+
56
+ try {
57
+ await login(page, credentials, loginUrl);
58
+ } catch (loginError) {
59
+ findings.push({
60
+ id: `${this.agentId}-login-failed`,
61
+ type: 'infra-issue',
62
+ severity: 'medium',
63
+ agentId: this.agentId,
64
+ module: 'workflow-completion-tester',
65
+ description: `Login failed — skipping workflow tests: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
66
+ });
67
+ return findings;
68
+ }
69
+
70
+ for (const workflow of this.config.workflows) {
71
+ const workflowFindings = await this.runWorkflow(page, workflow.name, workflow.steps);
72
+ findings.push(...workflowFindings);
73
+ }
74
+ } catch (playwrightError) {
75
+ findings.push({
76
+ id: `${this.agentId}-playwright-failure`,
77
+ type: 'infra-issue',
78
+ severity: 'medium',
79
+ agentId: this.agentId,
80
+ module: 'workflow-completion-tester',
81
+ description: `Workflow Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
82
+ });
83
+ } finally {
84
+ if (page) await page.close().catch(() => undefined);
85
+ if (browser) await browser.close().catch(() => undefined);
86
+ }
87
+
88
+ return findings;
89
+ }
90
+
91
+ private async runWorkflow(
92
+ page: Page,
93
+ workflowName: string,
94
+ steps: ValidatedConfig['workflows'][number]['steps'],
95
+ ): Promise<Finding[]> {
96
+ const findings: Finding[] = [];
97
+
98
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
99
+ const step = steps[stepIndex];
100
+
101
+ try {
102
+ await this.executeStep(page, step);
103
+ } catch (err) {
104
+ const description = err instanceof Error ? err.message : String(err);
105
+
106
+ const screenshotPath = await captureScreenshot(
107
+ page,
108
+ `workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}-failure`,
109
+ this.runDir,
110
+ ).catch(() => '');
111
+
112
+ if (screenshotPath) {
113
+ this.addEvidence({
114
+ type: 'screenshot',
115
+ path: screenshotPath,
116
+ description: `[Workflow "${workflowName}"] Failure screenshot at step ${stepIndex} (${step.action}: ${step.target})`,
117
+ });
118
+ }
119
+
120
+ findings.push({
121
+ id: `${this.agentId}-workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}`,
122
+ type: 'test-bug',
123
+ severity: 'medium',
124
+ agentId: this.agentId,
125
+ module: `workflow:${workflowName}`,
126
+ description: `Workflow "${workflowName}" failed at step ${stepIndex} (${step.action}: ${step.target}): ${description}`,
127
+ screenshot: screenshotPath || undefined,
128
+ });
129
+
130
+ // Stop processing this workflow on failure
131
+ break;
132
+ }
133
+
134
+ // Screenshot evidence at each step
135
+ const screenshotPath = await captureScreenshot(
136
+ page,
137
+ `workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}`,
138
+ this.runDir,
139
+ ).catch(() => '');
140
+
141
+ if (screenshotPath) {
142
+ this.addEvidence({
143
+ type: 'screenshot',
144
+ path: screenshotPath,
145
+ description: `[Workflow "${workflowName}"] Step ${stepIndex} completed (${step.action}: ${step.target})`,
146
+ });
147
+ }
148
+ }
149
+
150
+ return findings;
151
+ }
152
+
153
+ private async executeStep(
154
+ page: Page,
155
+ step: ValidatedConfig['workflows'][number]['steps'][number],
156
+ ): Promise<void> {
157
+ switch (step.action) {
158
+ case 'navigate': {
159
+ const url = step.target.startsWith('http') ? step.target : `${this.baseUrl}${step.target}`;
160
+ await page.goto(url);
161
+ await waitForSectionLoad(page);
162
+ break;
163
+ }
164
+
165
+ case 'click': {
166
+ await page.click(step.target);
167
+ if (step.verify) {
168
+ await page.waitForSelector(step.verify, { timeout: step.timeout ?? 5_000 });
169
+ }
170
+ break;
171
+ }
172
+
173
+ case 'fill-form': {
174
+ await fillForm(page, step.target, step.data ? { data: step.data } : undefined);
175
+ break;
176
+ }
177
+
178
+ case 'wait': {
179
+ await page.waitForSelector(step.target, { timeout: step.timeout ?? 5_000 });
180
+ break;
181
+ }
182
+
183
+ case 'verify': {
184
+ const element = await page.waitForSelector(step.target, { timeout: step.timeout ?? 5_000 });
185
+ if (step.contains !== undefined) {
186
+ const text = await element.textContent();
187
+ if (!text?.includes(step.contains)) {
188
+ throw new Error(
189
+ `Element "${step.target}" does not contain expected text "${step.contains}" — found: "${text ?? ''}"`,
190
+ );
191
+ }
192
+ }
193
+ break;
194
+ }
195
+
196
+ default: {
197
+ // TypeScript exhaustiveness guard
198
+ const _exhaustive: never = step.action;
199
+ throw new Error(`Unknown workflow step action: ${String(_exhaustive)}`);
200
+ }
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,262 @@
1
+ import type { Browser, BrowserContext, Page } from 'playwright';
2
+ import type { Finding } from '../core/types';
3
+ import { BaseAgent } from './base-agent';
4
+ import { resolveEnvironment } from '../helpers/env-resolver';
5
+
6
+ export class StateSessionTesterAgent extends BaseAgent {
7
+ readonly agentId = 21;
8
+ readonly agentName = 'State & Session Tester';
9
+ private baseUrl = '';
10
+
11
+ protected async preFlight(): Promise<void> {
12
+ const { env } = await resolveEnvironment(this.config, this.phase);
13
+ this.baseUrl = env.baseUrl;
14
+ }
15
+
16
+ protected async execute(): Promise<Finding[]> {
17
+ const findings: Finding[] = [];
18
+
19
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
20
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
21
+ const credentials =
22
+ this.config.auth.credentials?.['admin'] ??
23
+ Object.values(this.config.auth.credentials ?? {})[0];
24
+
25
+ if (!credentials) {
26
+ findings.push({
27
+ id: `${this.agentId}-no-credentials`,
28
+ type: 'infra-issue',
29
+ severity: 'critical',
30
+ agentId: this.agentId,
31
+ module: 'state-session',
32
+ description: 'No credentials configured — cannot run state/session tests',
33
+ });
34
+ return findings;
35
+ }
36
+
37
+ if (this.config.modules.length === 0) {
38
+ findings.push({
39
+ id: `${this.agentId}-no-modules`,
40
+ type: 'infra-issue',
41
+ severity: 'medium',
42
+ agentId: this.agentId,
43
+ module: 'state-session',
44
+ description: 'No modules configured — skipping state/session tests',
45
+ });
46
+ return findings;
47
+ }
48
+
49
+ const { chromium } = await import('playwright');
50
+
51
+ // -----------------------------------------------------------------------
52
+ // Scenario 1: Deep link — open module URL directly in a fresh context
53
+ // -----------------------------------------------------------------------
54
+ {
55
+ let browser: Browser | null = null;
56
+ let context: BrowserContext | null = null;
57
+ let page: Page | null = null;
58
+ try {
59
+ browser = await chromium.launch({ headless: true });
60
+ context = await browser.newContext();
61
+ page = await context.newPage();
62
+
63
+ const targetModule = this.config.modules[0]!;
64
+ const deepLinkUrl = `${this.baseUrl}${targetModule.route}`;
65
+
66
+ // Navigate directly to a deep link without prior login
67
+ await page.goto(deepLinkUrl, { waitUntil: 'networkidle', timeout: 20_000 });
68
+
69
+ // After potential login redirect and re-authentication, check we eventually load the module
70
+ const finalUrl = page.url();
71
+ const isOnLogin =
72
+ finalUrl.includes('/login') ||
73
+ finalUrl.includes('/auth') ||
74
+ finalUrl.includes('/signin');
75
+
76
+ if (!isOnLogin) {
77
+ // We either loaded the module or landed somewhere unexpected without login
78
+ const pageContent = await page.content();
79
+ const moduleLoaded =
80
+ pageContent.includes(targetModule.id) ||
81
+ pageContent.includes(targetModule.route.replace('/', ''));
82
+
83
+ if (!moduleLoaded) {
84
+ findings.push({
85
+ id: `${this.agentId}-deep-link-${targetModule.id}`,
86
+ type: 'code-bug-logic',
87
+ severity: 'medium',
88
+ agentId: this.agentId,
89
+ module: targetModule.id,
90
+ description:
91
+ `Deep link scenario: navigating directly to "${deepLinkUrl}" did not load the expected module ` +
92
+ `or redirect to login. Final URL: ${finalUrl}`,
93
+ suggestedFix:
94
+ 'Ensure unauthenticated deep links redirect to login then back to the original route.',
95
+ });
96
+ }
97
+ }
98
+ // Redirect to login on direct deep link is the correct behaviour — no finding
99
+ } catch (error) {
100
+ findings.push({
101
+ id: `${this.agentId}-deep-link-error`,
102
+ type: 'infra-issue',
103
+ severity: 'medium',
104
+ agentId: this.agentId,
105
+ module: this.config.modules[0]?.id ?? 'unknown',
106
+ description: `Deep link scenario failed: ${error instanceof Error ? error.message : String(error)}`,
107
+ });
108
+ } finally {
109
+ if (page) await page.close().catch(() => undefined);
110
+ if (context) await context.close().catch(() => undefined);
111
+ if (browser) await browser.close().catch(() => undefined);
112
+ }
113
+ }
114
+
115
+ // -----------------------------------------------------------------------
116
+ // Scenario 2: Refresh persistence — navigate to a module, reload, verify
117
+ // -----------------------------------------------------------------------
118
+ {
119
+ let browser: Browser | null = null;
120
+ let context: BrowserContext | null = null;
121
+ let page: Page | null = null;
122
+ try {
123
+ browser = await chromium.launch({ headless: true });
124
+ context = await browser.newContext();
125
+ page = await context.newPage();
126
+
127
+ // Login first
128
+ await page.goto(loginUrl, { waitUntil: 'networkidle', timeout: 20_000 });
129
+ await page
130
+ .fill('input[type="email"], input[name="email"]', credentials.email)
131
+ .catch(() => undefined);
132
+ await page
133
+ .fill('input[type="password"], input[name="password"]', credentials.password)
134
+ .catch(() => undefined);
135
+ await page.click('button[type="submit"]').catch(() => undefined);
136
+ await page
137
+ .waitForNavigation({ waitUntil: 'networkidle', timeout: 15_000 })
138
+ .catch(() => undefined);
139
+
140
+ const targetModule = this.config.modules[0]!;
141
+ const moduleUrl = `${this.baseUrl}${targetModule.route}`;
142
+
143
+ await page.goto(moduleUrl, { waitUntil: 'networkidle', timeout: 15_000 });
144
+ const urlBeforeReload = page.url();
145
+
146
+ // Reload the page
147
+ await page.reload({ waitUntil: 'networkidle', timeout: 15_000 });
148
+
149
+ const urlAfterReload = page.url();
150
+ const reloadedToLogin =
151
+ urlAfterReload.includes('/login') ||
152
+ urlAfterReload.includes('/auth') ||
153
+ urlAfterReload.includes('/signin');
154
+
155
+ if (reloadedToLogin) {
156
+ findings.push({
157
+ id: `${this.agentId}-refresh-${targetModule.id}`,
158
+ type: 'code-bug-logic',
159
+ severity: 'medium',
160
+ agentId: this.agentId,
161
+ module: targetModule.id,
162
+ description:
163
+ `Refresh persistence: page reload on "${urlBeforeReload}" redirected to login (${urlAfterReload}). ` +
164
+ 'Session was not persisted across a page refresh.',
165
+ suggestedFix:
166
+ 'Store the authentication token in a persistent storage (localStorage or httpOnly cookie) and restore session on reload.',
167
+ });
168
+ }
169
+ } catch (error) {
170
+ findings.push({
171
+ id: `${this.agentId}-refresh-error`,
172
+ type: 'infra-issue',
173
+ severity: 'medium',
174
+ agentId: this.agentId,
175
+ module: this.config.modules[0]?.id ?? 'unknown',
176
+ description: `Refresh persistence scenario failed: ${error instanceof Error ? error.message : String(error)}`,
177
+ });
178
+ } finally {
179
+ if (page) await page.close().catch(() => undefined);
180
+ if (context) await context.close().catch(() => undefined);
181
+ if (browser) await browser.close().catch(() => undefined);
182
+ }
183
+ }
184
+
185
+ // -----------------------------------------------------------------------
186
+ // Scenario 3: Back/forward navigation — navigate 3 modules, go back 2
187
+ // -----------------------------------------------------------------------
188
+ if (this.config.modules.length >= 3) {
189
+ let browser: Browser | null = null;
190
+ let context: BrowserContext | null = null;
191
+ let page: Page | null = null;
192
+ try {
193
+ browser = await chromium.launch({ headless: true });
194
+ context = await browser.newContext();
195
+ page = await context.newPage();
196
+
197
+ // Login first
198
+ await page.goto(loginUrl, { waitUntil: 'networkidle', timeout: 20_000 });
199
+ await page
200
+ .fill('input[type="email"], input[name="email"]', credentials.email)
201
+ .catch(() => undefined);
202
+ await page
203
+ .fill('input[type="password"], input[name="password"]', credentials.password)
204
+ .catch(() => undefined);
205
+ await page.click('button[type="submit"]').catch(() => undefined);
206
+ await page
207
+ .waitForNavigation({ waitUntil: 'networkidle', timeout: 15_000 })
208
+ .catch(() => undefined);
209
+
210
+ const [mod1, mod2, mod3] = this.config.modules.slice(0, 3) as [
211
+ (typeof this.config.modules)[0],
212
+ (typeof this.config.modules)[0],
213
+ (typeof this.config.modules)[0],
214
+ ];
215
+
216
+ await page.goto(`${this.baseUrl}${mod1.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
217
+ await page.goto(`${this.baseUrl}${mod2.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
218
+ await page.goto(`${this.baseUrl}${mod3.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
219
+
220
+ // Go back 2 pages — should land on mod1
221
+ await page.goBack({ waitUntil: 'networkidle', timeout: 10_000 });
222
+ await page.goBack({ waitUntil: 'networkidle', timeout: 10_000 });
223
+
224
+ const currentUrl = page.url();
225
+ const expectedRoute = mod1.route;
226
+ const landedOnCorrectModule =
227
+ currentUrl.includes(expectedRoute) ||
228
+ currentUrl.endsWith(expectedRoute.replace(/^\//, ''));
229
+
230
+ if (!landedOnCorrectModule) {
231
+ findings.push({
232
+ id: `${this.agentId}-back-forward-${mod1.id}`,
233
+ type: 'code-bug-logic',
234
+ severity: 'low',
235
+ agentId: this.agentId,
236
+ module: mod1.id,
237
+ description:
238
+ `Back/forward navigation: after navigating to modules [${mod1.id}, ${mod2.id}, ${mod3.id}] ` +
239
+ `and going back 2 times, expected to be on "${mod1.route}" but landed on "${currentUrl}".`,
240
+ suggestedFix:
241
+ 'Ensure router history is correctly maintained across module transitions.',
242
+ });
243
+ }
244
+ } catch (error) {
245
+ findings.push({
246
+ id: `${this.agentId}-back-forward-error`,
247
+ type: 'infra-issue',
248
+ severity: 'low',
249
+ agentId: this.agentId,
250
+ module: 'navigation',
251
+ description: `Back/forward scenario failed: ${error instanceof Error ? error.message : String(error)}`,
252
+ });
253
+ } finally {
254
+ if (page) await page.close().catch(() => undefined);
255
+ if (context) await context.close().catch(() => undefined);
256
+ if (browser) await browser.close().catch(() => undefined);
257
+ }
258
+ }
259
+
260
+ return findings;
261
+ }
262
+ }