@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,492 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Stub config
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
const stubConfig: ValidatedConfig = {
|
|
9
|
+
schemaVersion: 1,
|
|
10
|
+
name: 'test',
|
|
11
|
+
auth: {
|
|
12
|
+
loginUrl: 'http://localhost/login',
|
|
13
|
+
credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
|
|
14
|
+
},
|
|
15
|
+
modules: [
|
|
16
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
17
|
+
{ id: 'invoices', route: '/invoices', sidebarIcon: true },
|
|
18
|
+
],
|
|
19
|
+
environments: {
|
|
20
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
21
|
+
},
|
|
22
|
+
portals: [
|
|
23
|
+
{ name: 'client', queryParam: 'client' },
|
|
24
|
+
],
|
|
25
|
+
breakpoints: [1280, 768],
|
|
26
|
+
integrations: [],
|
|
27
|
+
workers: [],
|
|
28
|
+
workflows: [],
|
|
29
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
30
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
31
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
32
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
33
|
+
thresholds: {
|
|
34
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
35
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
36
|
+
apiResponseTime: 2000,
|
|
37
|
+
bundleSizeLimit: 500000,
|
|
38
|
+
maxConsoleErrors: 0,
|
|
39
|
+
},
|
|
40
|
+
disabledAgents: [],
|
|
41
|
+
disabledAgentSet: new Set<number>(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Mock child_process (for preFlight playwright version check)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const spawnMock = vi.fn();
|
|
49
|
+
vi.mock('child_process', () => ({
|
|
50
|
+
execFile: (...args: unknown[]) => spawnMock(...args),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Mock fetch (for isUrlReachable in preFlight)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
const fetchMock = vi.fn();
|
|
58
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Mock helpers
|
|
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 switchPortalMock = vi.fn().mockResolvedValue(undefined);
|
|
68
|
+
const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
69
|
+
const discoverInteractiveElementsMock = vi.fn().mockResolvedValue([]);
|
|
70
|
+
const fillFormMock = vi.fn().mockResolvedValue(undefined);
|
|
71
|
+
const openModalMock = vi.fn().mockResolvedValue(false);
|
|
72
|
+
const closeModalMock = vi.fn().mockResolvedValue(true);
|
|
73
|
+
|
|
74
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
75
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
76
|
+
navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
|
|
77
|
+
waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
|
|
78
|
+
switchPortal: (...args: unknown[]) => switchPortalMock(...args),
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
vi.mock('../../helpers/screenshot', () => ({
|
|
82
|
+
captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
vi.mock('../../helpers/element-discovery', () => ({
|
|
86
|
+
discoverInteractiveElements: (...args: unknown[]) => discoverInteractiveElementsMock(...args),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
vi.mock('../../helpers/form-filler', () => ({
|
|
90
|
+
fillForm: (...args: unknown[]) => fillFormMock(...args),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
vi.mock('../../helpers/modal-handler', () => ({
|
|
94
|
+
openModal: (...args: unknown[]) => openModalMock(...args),
|
|
95
|
+
closeModal: (...args: unknown[]) => closeModalMock(...args),
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Mock Playwright
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
type ConsoleListener = (msg: { type(): string; text(): string }) => void;
|
|
103
|
+
|
|
104
|
+
interface MockPage {
|
|
105
|
+
close: ReturnType<typeof vi.fn>;
|
|
106
|
+
on: ReturnType<typeof vi.fn>;
|
|
107
|
+
removeListener: ReturnType<typeof vi.fn>;
|
|
108
|
+
waitForTimeout: ReturnType<typeof vi.fn>;
|
|
109
|
+
setViewportSize: ReturnType<typeof vi.fn>;
|
|
110
|
+
_consoleListeners: ConsoleListener[];
|
|
111
|
+
emitConsole(type: string, text: string): void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function makeMockPage(): MockPage {
|
|
115
|
+
const consoleListeners: ConsoleListener[] = [];
|
|
116
|
+
|
|
117
|
+
const page: MockPage = {
|
|
118
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
119
|
+
on: vi.fn().mockImplementation((event: string, listener: ConsoleListener) => {
|
|
120
|
+
if (event === 'console') consoleListeners.push(listener);
|
|
121
|
+
}),
|
|
122
|
+
removeListener: vi.fn().mockImplementation((event: string, listener: ConsoleListener) => {
|
|
123
|
+
if (event === 'console') {
|
|
124
|
+
const idx = consoleListeners.indexOf(listener);
|
|
125
|
+
if (idx !== -1) consoleListeners.splice(idx, 1);
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
waitForTimeout: vi.fn().mockResolvedValue(undefined),
|
|
129
|
+
setViewportSize: vi.fn().mockResolvedValue(undefined),
|
|
130
|
+
_consoleListeners: consoleListeners,
|
|
131
|
+
emitConsole(type: string, text: string): void {
|
|
132
|
+
for (const l of consoleListeners) {
|
|
133
|
+
l({ type: () => type, text: () => text });
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return page;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let currentMockPage: MockPage;
|
|
142
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
143
|
+
|
|
144
|
+
vi.mock('playwright', async () => ({
|
|
145
|
+
chromium: {
|
|
146
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
147
|
+
},
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Dynamic import AFTER mocks are hoisted
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
const { BrowserCrawlerAgent } = await import('../05-browser-crawler');
|
|
155
|
+
|
|
156
|
+
function createAgent(): InstanceType<typeof BrowserCrawlerAgent> {
|
|
157
|
+
return new BrowserCrawlerAgent(stubConfig, 'beta', '/tmp/test-run');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Tests
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
describe('BrowserCrawlerAgent — Beta mode', () => {
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
spawnMock.mockReset();
|
|
167
|
+
fetchMock.mockReset();
|
|
168
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
169
|
+
navigateToSectionMock.mockReset().mockResolvedValue(undefined);
|
|
170
|
+
waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
|
|
171
|
+
switchPortalMock.mockReset().mockResolvedValue(undefined);
|
|
172
|
+
captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
173
|
+
discoverInteractiveElementsMock.mockReset().mockResolvedValue([]);
|
|
174
|
+
fillFormMock.mockReset().mockResolvedValue(undefined);
|
|
175
|
+
openModalMock.mockReset().mockResolvedValue(false);
|
|
176
|
+
closeModalMock.mockReset().mockResolvedValue(true);
|
|
177
|
+
|
|
178
|
+
currentMockPage = makeMockPage();
|
|
179
|
+
mockBrowser = {
|
|
180
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
181
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
spawnMock.mockImplementation(
|
|
185
|
+
(_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
186
|
+
cb(null, '1.40.0\n', '');
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
fetchMock.mockResolvedValue({ ok: true, status: 200 });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('enters beta mode and does not throw "not yet implemented"', async () => {
|
|
193
|
+
const agent = createAgent();
|
|
194
|
+
const result = await agent.run();
|
|
195
|
+
|
|
196
|
+
expect(result.findings.some(f => f.description.includes('not implemented'))).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('calls login once per breakpoint', async () => {
|
|
200
|
+
const agent = createAgent();
|
|
201
|
+
await agent.run();
|
|
202
|
+
|
|
203
|
+
// Beta mode uses a single desktop breakpoint [1280] regardless of config
|
|
204
|
+
expect(loginMock).toHaveBeenCalledTimes(1);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('calls navigateToSection for each module × each breakpoint pass', async () => {
|
|
208
|
+
const agent = createAgent();
|
|
209
|
+
await agent.run();
|
|
210
|
+
|
|
211
|
+
// 2 breakpoints × 2 modules = 4 bookkeeper calls, plus portal calls
|
|
212
|
+
// The bookkeeper pass alone is 2 × 2 = 4
|
|
213
|
+
const navigateCalls = navigateToSectionMock.mock.calls;
|
|
214
|
+
expect(navigateCalls.length).toBeGreaterThanOrEqual(
|
|
215
|
+
stubConfig.breakpoints.length * stubConfig.modules.length,
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('calls discoverInteractiveElements for each module visited', async () => {
|
|
220
|
+
const agent = createAgent();
|
|
221
|
+
await agent.run();
|
|
222
|
+
|
|
223
|
+
expect(discoverInteractiveElementsMock).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('takes a before and after screenshot for each module', async () => {
|
|
227
|
+
const agent = createAgent();
|
|
228
|
+
const result = await agent.run();
|
|
229
|
+
|
|
230
|
+
const screenshotEvidence = result.evidence.filter(e => e.type === 'screenshot');
|
|
231
|
+
// 2 breakpoints × 2 portals-passes × 2 modules × 2 (before+after) = at least 8
|
|
232
|
+
expect(screenshotEvidence.length).toBeGreaterThan(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('skips destructive elements and does not attempt to click them', async () => {
|
|
236
|
+
discoverInteractiveElementsMock.mockResolvedValue([
|
|
237
|
+
{
|
|
238
|
+
selector: '[data-testid="delete-btn"]',
|
|
239
|
+
type: 'button',
|
|
240
|
+
text: 'Delete',
|
|
241
|
+
isDestructive: true,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
selector: '[data-testid="save-btn"]',
|
|
245
|
+
type: 'button',
|
|
246
|
+
text: 'Save',
|
|
247
|
+
isDestructive: false,
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
const agent = createAgent();
|
|
252
|
+
await agent.run();
|
|
253
|
+
|
|
254
|
+
// openModal is called for non-destructive buttons; delete button must be skipped
|
|
255
|
+
const openModalCalls = openModalMock.mock.calls as Array<[unknown, string]>;
|
|
256
|
+
const clickedSelectors = openModalCalls.map(([, sel]) => sel);
|
|
257
|
+
expect(clickedSelectors).not.toContain('[data-testid="delete-btn"]');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('interacts with non-destructive button elements via openModal', async () => {
|
|
261
|
+
discoverInteractiveElementsMock.mockResolvedValue([
|
|
262
|
+
{
|
|
263
|
+
selector: '[data-testid="new-invoice"]',
|
|
264
|
+
type: 'button',
|
|
265
|
+
text: 'New Invoice',
|
|
266
|
+
isDestructive: false,
|
|
267
|
+
},
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
const agent = createAgent();
|
|
271
|
+
await agent.run();
|
|
272
|
+
|
|
273
|
+
expect(openModalMock).toHaveBeenCalledWith(currentMockPage, '[data-testid="new-invoice"]');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('calls closeModal when openModal returns true (modal was opened)', async () => {
|
|
277
|
+
discoverInteractiveElementsMock.mockResolvedValue([
|
|
278
|
+
{
|
|
279
|
+
selector: '[data-testid="open-modal"]',
|
|
280
|
+
type: 'button',
|
|
281
|
+
text: 'Open',
|
|
282
|
+
isDestructive: false,
|
|
283
|
+
},
|
|
284
|
+
]);
|
|
285
|
+
openModalMock.mockResolvedValue(true);
|
|
286
|
+
|
|
287
|
+
const agent = createAgent();
|
|
288
|
+
await agent.run();
|
|
289
|
+
|
|
290
|
+
expect(closeModalMock).toHaveBeenCalled();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('creates a console-error finding when a console error fires during interaction', async () => {
|
|
294
|
+
navigateToSectionMock.mockImplementation(
|
|
295
|
+
async (_page: unknown, moduleId: string) => {
|
|
296
|
+
if (moduleId === 'dashboard') {
|
|
297
|
+
currentMockPage.emitConsole('error', 'Uncaught ReferenceError: foo');
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const agent = createAgent();
|
|
303
|
+
const result = await agent.run();
|
|
304
|
+
|
|
305
|
+
const consoleFinding = result.findings.find(f => f.consoleError !== undefined);
|
|
306
|
+
expect(consoleFinding).toBeDefined();
|
|
307
|
+
expect(consoleFinding!.severity).toBe('high');
|
|
308
|
+
expect(consoleFinding!.consoleError).toBe('Uncaught ReferenceError: foo');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('calls switchPortal for configured portal passes', async () => {
|
|
312
|
+
const agent = createAgent();
|
|
313
|
+
await agent.run();
|
|
314
|
+
|
|
315
|
+
// Beta mode uses a single desktop breakpoint, so 1 portal pass
|
|
316
|
+
expect(switchPortalMock).toHaveBeenCalledTimes(1);
|
|
317
|
+
expect(switchPortalMock).toHaveBeenCalledWith(currentMockPage, 'client');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('returns passed status when no errors are found', async () => {
|
|
321
|
+
const agent = createAgent();
|
|
322
|
+
const result = await agent.run();
|
|
323
|
+
|
|
324
|
+
expect(result.status).toBe('passed');
|
|
325
|
+
expect(result.findings).toHaveLength(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('uses default breakpoint [1280] when breakpoints array is empty', async () => {
|
|
329
|
+
const configNoBreakpoints: ValidatedConfig = {
|
|
330
|
+
...stubConfig,
|
|
331
|
+
breakpoints: [],
|
|
332
|
+
portals: [],
|
|
333
|
+
};
|
|
334
|
+
const agent = new BrowserCrawlerAgent(configNoBreakpoints, 'beta', '/tmp/test-run');
|
|
335
|
+
await agent.run();
|
|
336
|
+
|
|
337
|
+
// Should still call login once (for the single default-breakpoint pass)
|
|
338
|
+
expect(loginMock).toHaveBeenCalledTimes(1);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// -------------------------------------------------------------------------
|
|
342
|
+
// Beta: no environment configured
|
|
343
|
+
// -------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
it('handles empty environments gracefully via resolveEnvironment fallback', async () => {
|
|
346
|
+
const configNoEnv: ValidatedConfig = {
|
|
347
|
+
...stubConfig,
|
|
348
|
+
environments: {},
|
|
349
|
+
};
|
|
350
|
+
// resolveEnvironment will throw or return a fallback — preFlight catches it
|
|
351
|
+
fetchMock.mockRejectedValue(new Error('no env'));
|
|
352
|
+
const agent = new BrowserCrawlerAgent(configNoEnv, 'beta', '/tmp/test-run');
|
|
353
|
+
const result = await agent.run();
|
|
354
|
+
|
|
355
|
+
// Either fails in preFlight or returns critical finding
|
|
356
|
+
expect(result.status === 'failed' || result.findings.some(f => f.severity === 'critical')).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// -------------------------------------------------------------------------
|
|
360
|
+
// Beta: no credentials configured
|
|
361
|
+
// -------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
it('creates critical finding when no credentials are configured', async () => {
|
|
364
|
+
const configNoCreds: ValidatedConfig = {
|
|
365
|
+
...stubConfig,
|
|
366
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
367
|
+
};
|
|
368
|
+
const agent = new BrowserCrawlerAgent(configNoCreds, 'beta', '/tmp/test-run');
|
|
369
|
+
const result = await agent.run();
|
|
370
|
+
|
|
371
|
+
expect(result.status).toBe('failed');
|
|
372
|
+
const finding = result.findings.find(f => f.id === '5-beta-no-credentials');
|
|
373
|
+
expect(finding).toBeDefined();
|
|
374
|
+
expect(finding!.severity).toBe('critical');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// -------------------------------------------------------------------------
|
|
378
|
+
// Beta: login failure with fallback severity
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
it('creates a login finding when login throws in beta mode', async () => {
|
|
382
|
+
loginMock.mockRejectedValue(new Error('Login failed'));
|
|
383
|
+
// Beta mode needs page.goto for fallback anonymous crawling
|
|
384
|
+
(currentMockPage as unknown as Record<string, unknown>).goto = vi.fn().mockResolvedValue(undefined);
|
|
385
|
+
|
|
386
|
+
const agent = createAgent();
|
|
387
|
+
const result = await agent.run();
|
|
388
|
+
|
|
389
|
+
// Login failure in beta produces a finding that mentions "Login failed"
|
|
390
|
+
const loginFinding = result.findings.find(f =>
|
|
391
|
+
f.description.includes('Login failed') || f.id.includes('login-failed'),
|
|
392
|
+
);
|
|
393
|
+
expect(loginFinding).toBeDefined();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// -------------------------------------------------------------------------
|
|
397
|
+
// Beta: navigation timeout in interaction pass
|
|
398
|
+
// -------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
it('creates a medium finding for navigation timeout (5s)', async () => {
|
|
401
|
+
navigateToSectionMock.mockImplementation(async () => {
|
|
402
|
+
return new Promise((_resolve, reject) =>
|
|
403
|
+
setTimeout(() => reject(new Error('Navigation timed out after 5s')), 10),
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const agent = createAgent();
|
|
408
|
+
const result = await agent.run();
|
|
409
|
+
|
|
410
|
+
const timeoutFinding = result.findings.find(f =>
|
|
411
|
+
f.description.includes('exceeded 5s timeout'),
|
|
412
|
+
);
|
|
413
|
+
expect(timeoutFinding).toBeDefined();
|
|
414
|
+
expect(timeoutFinding!.severity).toBe('medium');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('creates a high finding for non-timeout navigation failure', async () => {
|
|
418
|
+
navigateToSectionMock.mockRejectedValue(new Error('Element not found'));
|
|
419
|
+
|
|
420
|
+
const agent = createAgent();
|
|
421
|
+
const result = await agent.run();
|
|
422
|
+
|
|
423
|
+
const navFinding = result.findings.find(f =>
|
|
424
|
+
f.id.includes('beta-nav') && f.severity === 'high',
|
|
425
|
+
);
|
|
426
|
+
expect(navFinding).toBeDefined();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// -------------------------------------------------------------------------
|
|
430
|
+
// Beta: input/textarea/select element interaction via fillForm
|
|
431
|
+
// -------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
it('calls fillForm for input elements', async () => {
|
|
434
|
+
discoverInteractiveElementsMock.mockResolvedValue([
|
|
435
|
+
{
|
|
436
|
+
selector: 'input[name="email"]',
|
|
437
|
+
type: 'input',
|
|
438
|
+
text: '',
|
|
439
|
+
isDestructive: false,
|
|
440
|
+
},
|
|
441
|
+
]);
|
|
442
|
+
|
|
443
|
+
const agent = createAgent();
|
|
444
|
+
await agent.run();
|
|
445
|
+
|
|
446
|
+
expect(fillFormMock).toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// -------------------------------------------------------------------------
|
|
450
|
+
// Beta: modal opened then closed via waitForTimeout
|
|
451
|
+
// -------------------------------------------------------------------------
|
|
452
|
+
|
|
453
|
+
it('waits after interaction when openModal returns false (no modal)', async () => {
|
|
454
|
+
discoverInteractiveElementsMock.mockResolvedValue([
|
|
455
|
+
{
|
|
456
|
+
selector: '[data-testid="tab-1"]',
|
|
457
|
+
type: 'tab',
|
|
458
|
+
text: 'Tab 1',
|
|
459
|
+
isDestructive: false,
|
|
460
|
+
},
|
|
461
|
+
]);
|
|
462
|
+
openModalMock.mockResolvedValue(false);
|
|
463
|
+
|
|
464
|
+
const agent = createAgent();
|
|
465
|
+
await agent.run();
|
|
466
|
+
|
|
467
|
+
expect(currentMockPage.waitForTimeout).toHaveBeenCalledWith(1000);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// -------------------------------------------------------------------------
|
|
471
|
+
// Beta: limits to first 5 modules
|
|
472
|
+
// -------------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
it('limits interaction pass to first 5 modules in beta', async () => {
|
|
475
|
+
const manyModules = Array.from({ length: 8 }, (_, i) => ({
|
|
476
|
+
id: `mod-${i}`,
|
|
477
|
+
route: `/mod-${i}`,
|
|
478
|
+
sidebarIcon: true,
|
|
479
|
+
}));
|
|
480
|
+
const configManyModules: ValidatedConfig = {
|
|
481
|
+
...stubConfig,
|
|
482
|
+
modules: manyModules,
|
|
483
|
+
portals: [],
|
|
484
|
+
};
|
|
485
|
+
const agent = new BrowserCrawlerAgent(configManyModules, 'beta', '/tmp/test-run');
|
|
486
|
+
await agent.run();
|
|
487
|
+
|
|
488
|
+
// Beta limits to 5 modules for the interaction pass
|
|
489
|
+
// navigateToSection is called once per module in the interaction pass
|
|
490
|
+
expect(navigateToSectionMock.mock.calls.length).toBeLessThanOrEqual(5);
|
|
491
|
+
});
|
|
492
|
+
});
|