@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.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/agents/01-analyst.ts +100 -0
- package/agents/02-seed-architect.ts +59 -0
- package/agents/03-test-generator.ts +191 -0
- package/agents/04-unit-runner.ts +160 -0
- package/agents/05-browser-crawler.ts +790 -0
- package/agents/06-api-exerciser.ts +311 -0
- package/agents/07-security-scout.ts +188 -0
- package/agents/08-a11y-guardian.ts +212 -0
- package/agents/09-healer.ts +228 -0
- package/agents/10-reporter.ts +266 -0
- package/agents/11-fixer.ts +253 -0
- package/agents/12-ux-inspector.ts +444 -0
- package/agents/13-performance-profiler.ts +271 -0
- package/agents/14-data-integrity-auditor.ts +417 -0
- package/agents/15-regression-sentinel.ts +307 -0
- package/agents/16-chaos-agent.ts +228 -0
- package/agents/17-documentation-validator.ts +266 -0
- package/agents/18-integration-watchdog.ts +178 -0
- package/agents/19-tenant-isolation-auditor.ts +199 -0
- package/agents/20-workflow-completion-tester.ts +203 -0
- package/agents/21-state-session-tester.ts +262 -0
- package/agents/22-email-notification-verifier.ts +244 -0
- package/agents/23-migration-tester.ts +80 -0
- package/agents/__tests__/01-analyst.test.ts +188 -0
- package/agents/__tests__/02-seed-architect.test.ts +152 -0
- package/agents/__tests__/03-test-generator-full.test.ts +321 -0
- package/agents/__tests__/03-test-generator.test.ts +318 -0
- package/agents/__tests__/04-unit-runner.test.ts +320 -0
- package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
- package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
- package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
- package/agents/__tests__/05-browser-crawler.test.ts +518 -0
- package/agents/__tests__/06-api-exerciser.test.ts +619 -0
- package/agents/__tests__/07-security-scout.test.ts +382 -0
- package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
- package/agents/__tests__/09-healer.test.ts +384 -0
- package/agents/__tests__/10-reporter.test.ts +366 -0
- package/agents/__tests__/11-fixer.test.ts +406 -0
- package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
- package/agents/__tests__/12-ux-inspector.test.ts +443 -0
- package/agents/__tests__/13-performance-profiler.test.ts +411 -0
- package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
- package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
- package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
- package/agents/__tests__/16-chaos-agent.test.ts +427 -0
- package/agents/__tests__/17-documentation-validator.test.ts +402 -0
- package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
- package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
- package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
- package/agents/__tests__/21-state-session-tester.test.ts +374 -0
- package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
- package/agents/__tests__/23-migration-tester.test.ts +145 -0
- package/agents/__tests__/base-agent.test.ts +188 -0
- package/agents/__tests__/registry.test.ts +218 -0
- package/agents/base-agent.ts +77 -0
- package/agents/registry.ts +136 -0
- package/baselines/api-schemas/.gitkeep +0 -0
- package/baselines/performance/.gitkeep +0 -0
- package/baselines/screenshots/.gitkeep +0 -0
- package/bin/testteam.js +10 -0
- package/core/__tests__/ci-output.test.ts +430 -0
- package/core/__tests__/cli.test.ts +387 -0
- package/core/__tests__/config.test.ts +78 -0
- package/core/__tests__/cost-tracker.test.ts +158 -0
- package/core/__tests__/evidence.test.ts +265 -0
- package/core/__tests__/fix-loop.test.ts +210 -0
- package/core/__tests__/health-check.test.ts +44 -0
- package/core/__tests__/init.test.ts +609 -0
- package/core/__tests__/integration.test.ts +204 -0
- package/core/__tests__/license-gen.test.ts +227 -0
- package/core/__tests__/license.test.ts +326 -0
- package/core/__tests__/multi-browser.test.ts +278 -0
- package/core/__tests__/orchestrator.test.ts +519 -0
- package/core/__tests__/phase-gate.test.ts +43 -0
- package/core/__tests__/report-html.test.ts +398 -0
- package/core/__tests__/report-upload.test.ts +325 -0
- package/core/__tests__/run-counter.test.ts +234 -0
- package/core/ci-output.ts +240 -0
- package/core/cli.ts +232 -0
- package/core/config.ts +178 -0
- package/core/cost-tracker.ts +59 -0
- package/core/evidence.ts +132 -0
- package/core/fix-loop.ts +85 -0
- package/core/health-check.ts +54 -0
- package/core/init.ts +546 -0
- package/core/license-gen.ts +212 -0
- package/core/license.ts +211 -0
- package/core/messages.ts +67 -0
- package/core/multi-browser.ts +136 -0
- package/core/orchestrator.ts +354 -0
- package/core/phase-gate.ts +55 -0
- package/core/report-html.ts +657 -0
- package/core/report-upload.ts +188 -0
- package/core/run-counter.ts +175 -0
- package/core/types.ts +57 -0
- package/dist/agents/01-analyst.d.ts +11 -0
- package/dist/agents/01-analyst.d.ts.map +1 -0
- package/dist/agents/01-analyst.js +75 -0
- package/dist/agents/01-analyst.js.map +1 -0
- package/dist/agents/02-seed-architect.d.ts +11 -0
- package/dist/agents/02-seed-architect.d.ts.map +1 -0
- package/dist/agents/02-seed-architect.js +51 -0
- package/dist/agents/02-seed-architect.js.map +1 -0
- package/dist/agents/03-test-generator.d.ts +9 -0
- package/dist/agents/03-test-generator.d.ts.map +1 -0
- package/dist/agents/03-test-generator.js +167 -0
- package/dist/agents/03-test-generator.js.map +1 -0
- package/dist/agents/04-unit-runner.d.ts +9 -0
- package/dist/agents/04-unit-runner.d.ts.map +1 -0
- package/dist/agents/04-unit-runner.js +113 -0
- package/dist/agents/04-unit-runner.js.map +1 -0
- package/dist/agents/05-browser-crawler.d.ts +30 -0
- package/dist/agents/05-browser-crawler.d.ts.map +1 -0
- package/dist/agents/05-browser-crawler.js +685 -0
- package/dist/agents/05-browser-crawler.js.map +1 -0
- package/dist/agents/06-api-exerciser.d.ts +23 -0
- package/dist/agents/06-api-exerciser.d.ts.map +1 -0
- package/dist/agents/06-api-exerciser.js +253 -0
- package/dist/agents/06-api-exerciser.js.map +1 -0
- package/dist/agents/07-security-scout.d.ts +11 -0
- package/dist/agents/07-security-scout.d.ts.map +1 -0
- package/dist/agents/07-security-scout.js +142 -0
- package/dist/agents/07-security-scout.js.map +1 -0
- package/dist/agents/08-a11y-guardian.d.ts +13 -0
- package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
- package/dist/agents/08-a11y-guardian.js +176 -0
- package/dist/agents/08-a11y-guardian.js.map +1 -0
- package/dist/agents/09-healer.d.ts +33 -0
- package/dist/agents/09-healer.d.ts.map +1 -0
- package/dist/agents/09-healer.js +167 -0
- package/dist/agents/09-healer.js.map +1 -0
- package/dist/agents/10-reporter.d.ts +26 -0
- package/dist/agents/10-reporter.d.ts.map +1 -0
- package/dist/agents/10-reporter.js +215 -0
- package/dist/agents/10-reporter.js.map +1 -0
- package/dist/agents/11-fixer.d.ts +26 -0
- package/dist/agents/11-fixer.d.ts.map +1 -0
- package/dist/agents/11-fixer.js +195 -0
- package/dist/agents/11-fixer.js.map +1 -0
- package/dist/agents/12-ux-inspector.d.ts +15 -0
- package/dist/agents/12-ux-inspector.d.ts.map +1 -0
- package/dist/agents/12-ux-inspector.js +364 -0
- package/dist/agents/12-ux-inspector.js.map +1 -0
- package/dist/agents/13-performance-profiler.d.ts +13 -0
- package/dist/agents/13-performance-profiler.d.ts.map +1 -0
- package/dist/agents/13-performance-profiler.js +216 -0
- package/dist/agents/13-performance-profiler.js.map +1 -0
- package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
- package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
- package/dist/agents/14-data-integrity-auditor.js +356 -0
- package/dist/agents/14-data-integrity-auditor.js.map +1 -0
- package/dist/agents/15-regression-sentinel.d.ts +25 -0
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
- package/dist/agents/15-regression-sentinel.js +251 -0
- package/dist/agents/15-regression-sentinel.js.map +1 -0
- package/dist/agents/16-chaos-agent.d.ts +9 -0
- package/dist/agents/16-chaos-agent.d.ts.map +1 -0
- package/dist/agents/16-chaos-agent.js +207 -0
- package/dist/agents/16-chaos-agent.js.map +1 -0
- package/dist/agents/17-documentation-validator.d.ts +31 -0
- package/dist/agents/17-documentation-validator.d.ts.map +1 -0
- package/dist/agents/17-documentation-validator.js +246 -0
- package/dist/agents/17-documentation-validator.js.map +1 -0
- package/dist/agents/18-integration-watchdog.d.ts +10 -0
- package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
- package/dist/agents/18-integration-watchdog.js +138 -0
- package/dist/agents/18-integration-watchdog.js.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.js +166 -0
- package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
- package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
- package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
- package/dist/agents/20-workflow-completion-tester.js +159 -0
- package/dist/agents/20-workflow-completion-tester.js.map +1 -0
- package/dist/agents/21-state-session-tester.d.ts +10 -0
- package/dist/agents/21-state-session-tester.d.ts.map +1 -0
- package/dist/agents/21-state-session-tester.js +233 -0
- package/dist/agents/21-state-session-tester.js.map +1 -0
- package/dist/agents/22-email-notification-verifier.d.ts +11 -0
- package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
- package/dist/agents/22-email-notification-verifier.js +199 -0
- package/dist/agents/22-email-notification-verifier.js.map +1 -0
- package/dist/agents/23-migration-tester.d.ts +10 -0
- package/dist/agents/23-migration-tester.d.ts.map +1 -0
- package/dist/agents/23-migration-tester.js +74 -0
- package/dist/agents/23-migration-tester.js.map +1 -0
- package/dist/agents/base-agent.d.ts +19 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +67 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/registry.d.ts +29 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +117 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/core/ci-output.d.ts +35 -0
- package/dist/core/ci-output.d.ts.map +1 -0
- package/dist/core/ci-output.js +193 -0
- package/dist/core/ci-output.js.map +1 -0
- package/dist/core/cli.d.ts +11 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +197 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +111 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +42 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +22 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +41 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/evidence.d.ts +28 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +95 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fix-loop.d.ts +29 -0
- package/dist/core/fix-loop.d.ts.map +1 -0
- package/dist/core/fix-loop.js +70 -0
- package/dist/core/fix-loop.js.map +1 -0
- package/dist/core/health-check.d.ts +21 -0
- package/dist/core/health-check.d.ts.map +1 -0
- package/dist/core/health-check.js +26 -0
- package/dist/core/health-check.js.map +1 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +435 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/license-gen.d.ts +12 -0
- package/dist/core/license-gen.d.ts.map +1 -0
- package/dist/core/license-gen.js +169 -0
- package/dist/core/license-gen.js.map +1 -0
- package/dist/core/license.d.ts +33 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +170 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +47 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/multi-browser.d.ts +36 -0
- package/dist/core/multi-browser.d.ts.map +1 -0
- package/dist/core/multi-browser.js +88 -0
- package/dist/core/multi-browser.js.map +1 -0
- package/dist/core/orchestrator.d.ts +48 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +291 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/phase-gate.d.ts +4 -0
- package/dist/core/phase-gate.d.ts.map +1 -0
- package/dist/core/phase-gate.js +39 -0
- package/dist/core/phase-gate.js.map +1 -0
- package/dist/core/report-html.d.ts +9 -0
- package/dist/core/report-html.d.ts.map +1 -0
- package/dist/core/report-html.js +617 -0
- package/dist/core/report-html.js.map +1 -0
- package/dist/core/report-upload.d.ts +16 -0
- package/dist/core/report-upload.d.ts.map +1 -0
- package/dist/core/report-upload.js +124 -0
- package/dist/core/report-upload.js.map +1 -0
- package/dist/core/run-counter.d.ts +40 -0
- package/dist/core/run-counter.d.ts.map +1 -0
- package/dist/core/run-counter.js +120 -0
- package/dist/core/run-counter.js.map +1 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/helpers/api-client.d.ts +30 -0
- package/dist/helpers/api-client.d.ts.map +1 -0
- package/dist/helpers/api-client.js +77 -0
- package/dist/helpers/api-client.js.map +1 -0
- package/dist/helpers/element-discovery.d.ts +18 -0
- package/dist/helpers/element-discovery.d.ts.map +1 -0
- package/dist/helpers/element-discovery.js +82 -0
- package/dist/helpers/element-discovery.js.map +1 -0
- package/dist/helpers/env-resolver.d.ts +29 -0
- package/dist/helpers/env-resolver.d.ts.map +1 -0
- package/dist/helpers/env-resolver.js +51 -0
- package/dist/helpers/env-resolver.js.map +1 -0
- package/dist/helpers/form-filler.d.ts +13 -0
- package/dist/helpers/form-filler.d.ts.map +1 -0
- package/dist/helpers/form-filler.js +98 -0
- package/dist/helpers/form-filler.js.map +1 -0
- package/dist/helpers/modal-handler.d.ts +16 -0
- package/dist/helpers/modal-handler.d.ts.map +1 -0
- package/dist/helpers/modal-handler.js +95 -0
- package/dist/helpers/modal-handler.js.map +1 -0
- package/dist/helpers/navigation.d.ts +37 -0
- package/dist/helpers/navigation.d.ts.map +1 -0
- package/dist/helpers/navigation.js +83 -0
- package/dist/helpers/navigation.js.map +1 -0
- package/dist/helpers/quality-gate.d.ts +17 -0
- package/dist/helpers/quality-gate.d.ts.map +1 -0
- package/dist/helpers/quality-gate.js +144 -0
- package/dist/helpers/quality-gate.js.map +1 -0
- package/dist/helpers/screenshot.d.ts +24 -0
- package/dist/helpers/screenshot.d.ts.map +1 -0
- package/dist/helpers/screenshot.js +76 -0
- package/dist/helpers/screenshot.js.map +1 -0
- package/dist/helpers/seed-validator.d.ts +15 -0
- package/dist/helpers/seed-validator.d.ts.map +1 -0
- package/dist/helpers/seed-validator.js +53 -0
- package/dist/helpers/seed-validator.js.map +1 -0
- package/helpers/__tests__/api-client.test.ts +199 -0
- package/helpers/__tests__/element-discovery.test.ts +202 -0
- package/helpers/__tests__/form-filler-extended.test.ts +212 -0
- package/helpers/__tests__/form-filler.test.ts +99 -0
- package/helpers/__tests__/modal-handler.test.ts +152 -0
- package/helpers/__tests__/navigation.test.ts +214 -0
- package/helpers/__tests__/quality-gate.test.ts +117 -0
- package/helpers/__tests__/screenshot.test.ts +139 -0
- package/helpers/__tests__/seed-validator.test.ts +114 -0
- package/helpers/api-client.ts +111 -0
- package/helpers/element-discovery.ts +105 -0
- package/helpers/env-resolver.ts +69 -0
- package/helpers/form-filler.ts +126 -0
- package/helpers/modal-handler.ts +108 -0
- package/helpers/navigation.ts +100 -0
- package/helpers/quality-gate.ts +180 -0
- package/helpers/screenshot.ts +111 -0
- package/helpers/seed-validator.ts +70 -0
- package/package.json +88 -0
|
@@ -0,0 +1,518 @@
|
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const stubConfig = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
name: 'test',
|
|
12
|
+
auth: {
|
|
13
|
+
loginUrl: 'http://localhost/login',
|
|
14
|
+
credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
|
|
15
|
+
},
|
|
16
|
+
modules: [
|
|
17
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
18
|
+
{ id: 'invoices', route: '/invoices', sidebarIcon: true },
|
|
19
|
+
],
|
|
20
|
+
environments: {
|
|
21
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
22
|
+
},
|
|
23
|
+
portals: [],
|
|
24
|
+
breakpoints: [],
|
|
25
|
+
integrations: [],
|
|
26
|
+
workers: [],
|
|
27
|
+
workflows: [],
|
|
28
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
29
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
30
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
|
|
31
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
32
|
+
thresholds: {
|
|
33
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
34
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
35
|
+
apiResponseTime: 2000,
|
|
36
|
+
bundleSizeLimit: 500000,
|
|
37
|
+
maxConsoleErrors: 0,
|
|
38
|
+
},
|
|
39
|
+
disabledAgents: [],
|
|
40
|
+
disabledAgentSet: new Set<number>(),
|
|
41
|
+
} satisfies ValidatedConfig;
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Mock child_process
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const spawnMock = vi.fn();
|
|
48
|
+
|
|
49
|
+
vi.mock('child_process', () => ({
|
|
50
|
+
execFile: (...args: unknown[]) => spawnMock(...args),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Mock fetch (used by isUrlReachable in preFlight)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
const fetchMock = vi.fn();
|
|
58
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Mock helpers used by the agent
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
65
|
+
const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
|
|
66
|
+
const waitForSectionLoadMock = vi.fn().mockResolvedValue(undefined);
|
|
67
|
+
const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
68
|
+
|
|
69
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
70
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
71
|
+
navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
|
|
72
|
+
waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
vi.mock('../../helpers/screenshot', () => ({
|
|
76
|
+
captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Mock Playwright
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
type ConsoleListener = (msg: { type(): string; text(): string }) => void;
|
|
84
|
+
type ResponseListener = (response: { status(): number; url(): string }) => void;
|
|
85
|
+
|
|
86
|
+
interface MockPage {
|
|
87
|
+
close: ReturnType<typeof vi.fn>;
|
|
88
|
+
on: ReturnType<typeof vi.fn>;
|
|
89
|
+
_consoleListeners: ConsoleListener[];
|
|
90
|
+
_responseListeners: ResponseListener[];
|
|
91
|
+
/** Helper: emit a console event to all listeners */
|
|
92
|
+
emitConsole(type: string, text: string): void;
|
|
93
|
+
/** Helper: emit a response event to all listeners */
|
|
94
|
+
emitResponse(status: number, url: string): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function makeMockPage(): MockPage {
|
|
98
|
+
const consoleListeners: ConsoleListener[] = [];
|
|
99
|
+
const responseListeners: ResponseListener[] = [];
|
|
100
|
+
|
|
101
|
+
const page: MockPage = {
|
|
102
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
103
|
+
on: vi.fn().mockImplementation((event: string, listener: ConsoleListener | ResponseListener) => {
|
|
104
|
+
if (event === 'console') consoleListeners.push(listener as ConsoleListener);
|
|
105
|
+
if (event === 'response') responseListeners.push(listener as ResponseListener);
|
|
106
|
+
}),
|
|
107
|
+
_consoleListeners: consoleListeners,
|
|
108
|
+
_responseListeners: responseListeners,
|
|
109
|
+
emitConsole(type: string, text: string): void {
|
|
110
|
+
for (const l of consoleListeners) {
|
|
111
|
+
l({ type: () => type, text: () => text });
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
emitResponse(status: number, url: string): void {
|
|
115
|
+
for (const l of responseListeners) {
|
|
116
|
+
l({ status: () => status, url: () => url });
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return page;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let currentMockPage: MockPage;
|
|
125
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
126
|
+
|
|
127
|
+
vi.mock('playwright', async () => {
|
|
128
|
+
return {
|
|
129
|
+
chromium: {
|
|
130
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Dynamic import AFTER mocks
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
const { BrowserCrawlerAgent } = await import('../05-browser-crawler');
|
|
140
|
+
|
|
141
|
+
function createAgent(phase: Phase = 'alpha'): InstanceType<typeof BrowserCrawlerAgent> {
|
|
142
|
+
return new BrowserCrawlerAgent(stubConfig, phase, '/tmp/test-run');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Tests
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('BrowserCrawlerAgent', () => {
|
|
150
|
+
beforeEach(() => {
|
|
151
|
+
spawnMock.mockReset();
|
|
152
|
+
fetchMock.mockReset();
|
|
153
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
154
|
+
navigateToSectionMock.mockReset().mockResolvedValue(undefined);
|
|
155
|
+
waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
|
|
156
|
+
captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
157
|
+
|
|
158
|
+
currentMockPage = makeMockPage();
|
|
159
|
+
mockBrowser = {
|
|
160
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
161
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Default: playwright version check succeeds
|
|
165
|
+
spawnMock.mockImplementation(
|
|
166
|
+
(_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
167
|
+
cb(null, '1.40.0\n', '');
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Default: base URL is reachable
|
|
172
|
+
fetchMock.mockResolvedValue({ ok: true, status: 200 });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('has agentId 5 and agentName "Browser Crawler"', () => {
|
|
176
|
+
const agent = createAgent();
|
|
177
|
+
expect(agent.agentId).toBe(5);
|
|
178
|
+
expect(agent.agentName).toBe('Browser Crawler');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// preFlight()
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
describe('preFlight()', () => {
|
|
186
|
+
it('throws when base URL is not reachable', async () => {
|
|
187
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
188
|
+
|
|
189
|
+
const agent = createAgent();
|
|
190
|
+
const result = await agent.run();
|
|
191
|
+
|
|
192
|
+
expect(result.status).toBe('failed');
|
|
193
|
+
expect(result.findings[0].description).toContain('not reachable');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('passes when playwright version check and fetch both succeed', async () => {
|
|
197
|
+
const agent = createAgent();
|
|
198
|
+
const result = await agent.run();
|
|
199
|
+
|
|
200
|
+
// preFlight must not have thrown — status driven by execute()
|
|
201
|
+
expect(result.findings.some((f) => f.description.includes('not reachable'))).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// -------------------------------------------------------------------------
|
|
206
|
+
// execute() — smoke / alpha mode
|
|
207
|
+
// -------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
describe('execute() (smoke / alpha mode)', () => {
|
|
210
|
+
it('calls login() once before iterating modules', async () => {
|
|
211
|
+
const agent = createAgent('alpha');
|
|
212
|
+
await agent.run();
|
|
213
|
+
|
|
214
|
+
expect(loginMock).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(loginMock).toHaveBeenCalledWith(
|
|
216
|
+
currentMockPage,
|
|
217
|
+
stubConfig.auth.credentials!['admin'],
|
|
218
|
+
stubConfig.auth.loginUrl,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('navigates to each configured module', async () => {
|
|
223
|
+
const agent = createAgent('alpha');
|
|
224
|
+
await agent.run();
|
|
225
|
+
|
|
226
|
+
expect(navigateToSectionMock).toHaveBeenCalledTimes(stubConfig.modules.length);
|
|
227
|
+
for (const mod of stubConfig.modules) {
|
|
228
|
+
expect(navigateToSectionMock).toHaveBeenCalledWith(
|
|
229
|
+
currentMockPage,
|
|
230
|
+
mod.id,
|
|
231
|
+
stubConfig,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('calls waitForSectionLoad for each module', async () => {
|
|
237
|
+
const agent = createAgent('alpha');
|
|
238
|
+
await agent.run();
|
|
239
|
+
|
|
240
|
+
expect(waitForSectionLoadMock).toHaveBeenCalledTimes(stubConfig.modules.length);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('takes a screenshot for each module', async () => {
|
|
244
|
+
const agent = createAgent('alpha');
|
|
245
|
+
await agent.run();
|
|
246
|
+
|
|
247
|
+
expect(captureScreenshotMock).toHaveBeenCalledTimes(stubConfig.modules.length);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('returns no findings when navigation succeeds and no console errors occur', async () => {
|
|
251
|
+
const agent = createAgent('alpha');
|
|
252
|
+
const result = await agent.run();
|
|
253
|
+
|
|
254
|
+
expect(result.findings).toHaveLength(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('returns passed status when no errors are found', async () => {
|
|
258
|
+
const agent = createAgent('alpha');
|
|
259
|
+
const result = await agent.run();
|
|
260
|
+
|
|
261
|
+
expect(result.status).toBe('passed');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('adds screenshot evidence items for each module', async () => {
|
|
265
|
+
const agent = createAgent('alpha');
|
|
266
|
+
const result = await agent.run();
|
|
267
|
+
|
|
268
|
+
const screenshotEvidence = result.evidence.filter((e) => e.type === 'screenshot');
|
|
269
|
+
expect(screenshotEvidence).toHaveLength(stubConfig.modules.length);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// -------------------------------------------------------------------------
|
|
274
|
+
// Console errors
|
|
275
|
+
// -------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
describe('console error handling', () => {
|
|
278
|
+
it('creates a high-severity finding for each console error', async () => {
|
|
279
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
280
|
+
if (moduleId === 'dashboard') {
|
|
281
|
+
currentMockPage.emitConsole('error', 'Uncaught TypeError: Cannot read properties of null');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const agent = createAgent('alpha');
|
|
286
|
+
const result = await agent.run();
|
|
287
|
+
|
|
288
|
+
const consoleFinding = result.findings.find((f) => f.consoleError !== undefined);
|
|
289
|
+
expect(consoleFinding).toBeDefined();
|
|
290
|
+
expect(consoleFinding!.severity).toBe('high');
|
|
291
|
+
expect(consoleFinding!.type).toBe('code-bug-logic');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('includes the console error text in the finding description', async () => {
|
|
295
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
296
|
+
if (moduleId === 'dashboard') {
|
|
297
|
+
currentMockPage.emitConsole('error', 'ReferenceError: foo is not defined');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const agent = createAgent('alpha');
|
|
302
|
+
const result = await agent.run();
|
|
303
|
+
|
|
304
|
+
const consoleFinding = result.findings.find((f) => f.consoleError !== undefined);
|
|
305
|
+
expect(consoleFinding!.description).toContain('ReferenceError: foo is not defined');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('does not create a finding for non-error console messages', async () => {
|
|
309
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
310
|
+
if (moduleId === 'dashboard') {
|
|
311
|
+
currentMockPage.emitConsole('log', 'App initialized');
|
|
312
|
+
currentMockPage.emitConsole('warn', 'Deprecated API');
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const agent = createAgent('alpha');
|
|
317
|
+
const result = await agent.run();
|
|
318
|
+
|
|
319
|
+
const consoleFindings = result.findings.filter((f) => f.consoleError !== undefined);
|
|
320
|
+
expect(consoleFindings).toHaveLength(0);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// -------------------------------------------------------------------------
|
|
325
|
+
// Network failures
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
describe('network failure handling', () => {
|
|
329
|
+
it('creates a finding for 4xx responses', async () => {
|
|
330
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
331
|
+
if (moduleId === 'invoices') {
|
|
332
|
+
currentMockPage.emitResponse(404, 'http://localhost:3000/api/invoices');
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const agent = createAgent('alpha');
|
|
337
|
+
const result = await agent.run();
|
|
338
|
+
|
|
339
|
+
const networkFinding = result.findings.find(
|
|
340
|
+
(f) => f.description.includes('404') || f.description.includes('network failure'),
|
|
341
|
+
);
|
|
342
|
+
expect(networkFinding).toBeDefined();
|
|
343
|
+
expect(networkFinding!.severity).toBe('high');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('creates a finding for 5xx responses', async () => {
|
|
347
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
348
|
+
if (moduleId === 'dashboard') {
|
|
349
|
+
currentMockPage.emitResponse(500, 'http://localhost:3000/api/health');
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const agent = createAgent('alpha');
|
|
354
|
+
const result = await agent.run();
|
|
355
|
+
|
|
356
|
+
const networkFinding = result.findings.find(
|
|
357
|
+
(f) => f.description.includes('500') || f.description.includes('network failure'),
|
|
358
|
+
);
|
|
359
|
+
expect(networkFinding).toBeDefined();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('does not create a finding for 2xx responses', async () => {
|
|
363
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
364
|
+
if (moduleId === 'dashboard') {
|
|
365
|
+
currentMockPage.emitResponse(200, 'http://localhost:3000/api/data');
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const agent = createAgent('alpha');
|
|
370
|
+
const result = await agent.run();
|
|
371
|
+
|
|
372
|
+
const networkFindings = result.findings.filter((f) => f.type === 'infra-issue');
|
|
373
|
+
expect(networkFindings).toHaveLength(0);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// -------------------------------------------------------------------------
|
|
378
|
+
// Navigation failure
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
describe('navigation failure', () => {
|
|
382
|
+
it('creates an infra-issue finding when navigation to a module throws', async () => {
|
|
383
|
+
navigateToSectionMock.mockImplementation(async (_page: unknown, moduleId: string) => {
|
|
384
|
+
if (moduleId === 'dashboard') {
|
|
385
|
+
throw new Error('Timeout waiting for page');
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const agent = createAgent('alpha');
|
|
390
|
+
const result = await agent.run();
|
|
391
|
+
|
|
392
|
+
const navFinding = result.findings.find(
|
|
393
|
+
(f) => f.id.includes('nav-dashboard'),
|
|
394
|
+
);
|
|
395
|
+
expect(navFinding).toBeDefined();
|
|
396
|
+
expect(navFinding!.type).toBe('infra-issue');
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// -------------------------------------------------------------------------
|
|
401
|
+
// Unsupported phase
|
|
402
|
+
// -------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
describe('unsupported phase', () => {
|
|
405
|
+
it('fails with error when phase is "beta" (not yet implemented)', async () => {
|
|
406
|
+
const agent = createAgent('beta');
|
|
407
|
+
const result = await agent.run();
|
|
408
|
+
|
|
409
|
+
expect(result.status).toBe('failed');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('fails with error when phase is "uat" (not yet implemented)', async () => {
|
|
413
|
+
const agent = createAgent('uat');
|
|
414
|
+
const result = await agent.run();
|
|
415
|
+
|
|
416
|
+
expect(result.status).toBe('failed');
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// -------------------------------------------------------------------------
|
|
421
|
+
// Smoke: no environment configured
|
|
422
|
+
// -------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
describe('smoke: no environment', () => {
|
|
425
|
+
it('fails when no environment is configured and URL is unreachable', async () => {
|
|
426
|
+
const noEnvConfig = { ...stubConfig, environments: {} };
|
|
427
|
+
fetchMock.mockRejectedValue(new Error('no env'));
|
|
428
|
+
const agent = new BrowserCrawlerAgent(noEnvConfig as ValidatedConfig, 'alpha', '/tmp/test-run');
|
|
429
|
+
const result = await agent.run();
|
|
430
|
+
|
|
431
|
+
// Either preFlight fails or execute produces critical finding
|
|
432
|
+
expect(
|
|
433
|
+
result.status === 'failed' ||
|
|
434
|
+
result.findings.some(f => f.severity === 'critical'),
|
|
435
|
+
).toBe(true);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// -------------------------------------------------------------------------
|
|
440
|
+
// Smoke: no credentials
|
|
441
|
+
// -------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
describe('smoke: no credentials', () => {
|
|
444
|
+
it('creates a critical finding when no credentials are configured', async () => {
|
|
445
|
+
const noCredConfig = {
|
|
446
|
+
...stubConfig,
|
|
447
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
448
|
+
};
|
|
449
|
+
const agent = new BrowserCrawlerAgent(noCredConfig as ValidatedConfig, 'alpha', '/tmp/test-run');
|
|
450
|
+
const result = await agent.run();
|
|
451
|
+
|
|
452
|
+
const finding = result.findings.find(f => f.id === '5-no-credentials');
|
|
453
|
+
expect(finding).toBeDefined();
|
|
454
|
+
expect(finding!.severity).toBe('critical');
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// -------------------------------------------------------------------------
|
|
459
|
+
// Smoke: login failure
|
|
460
|
+
// -------------------------------------------------------------------------
|
|
461
|
+
|
|
462
|
+
describe('smoke: login failure', () => {
|
|
463
|
+
it('creates a finding when login fails and continues crawling anonymously', async () => {
|
|
464
|
+
loginMock.mockRejectedValue(new Error('Auth service down'));
|
|
465
|
+
(currentMockPage as unknown as Record<string, unknown>).goto = vi.fn().mockResolvedValue(undefined);
|
|
466
|
+
|
|
467
|
+
const agent = createAgent('alpha');
|
|
468
|
+
const result = await agent.run();
|
|
469
|
+
|
|
470
|
+
const loginFinding = result.findings.find(f => f.id === '5-login-failed');
|
|
471
|
+
expect(loginFinding).toBeDefined();
|
|
472
|
+
// In alpha, login failure severity is medium
|
|
473
|
+
expect(loginFinding!.severity).toBe('medium');
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// -------------------------------------------------------------------------
|
|
478
|
+
// Smoke: login URL with full http prefix
|
|
479
|
+
// -------------------------------------------------------------------------
|
|
480
|
+
|
|
481
|
+
describe('smoke: absolute login URL', () => {
|
|
482
|
+
it('uses loginUrl directly when it starts with http', async () => {
|
|
483
|
+
const agent = createAgent('alpha');
|
|
484
|
+
await agent.run();
|
|
485
|
+
|
|
486
|
+
// loginUrl starts with http:// in our config, so should be used as-is
|
|
487
|
+
expect(loginMock).toHaveBeenCalledWith(
|
|
488
|
+
currentMockPage,
|
|
489
|
+
stubConfig.auth.credentials!['admin'],
|
|
490
|
+
'http://localhost/login',
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// -------------------------------------------------------------------------
|
|
496
|
+
// Smoke: relative login URL gets prefixed with baseUrl
|
|
497
|
+
// -------------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
describe('smoke: relative login URL', () => {
|
|
500
|
+
it('prefixes relative loginUrl with baseUrl', async () => {
|
|
501
|
+
const relativeLoginConfig = {
|
|
502
|
+
...stubConfig,
|
|
503
|
+
auth: {
|
|
504
|
+
loginUrl: '/login',
|
|
505
|
+
credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
const agent = new BrowserCrawlerAgent(relativeLoginConfig as ValidatedConfig, 'alpha', '/tmp/test-run');
|
|
509
|
+
await agent.run();
|
|
510
|
+
|
|
511
|
+
expect(loginMock).toHaveBeenCalledWith(
|
|
512
|
+
currentMockPage,
|
|
513
|
+
relativeLoginConfig.auth.credentials['admin'],
|
|
514
|
+
'http://localhost:3000/login',
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
});
|