@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,465 @@
|
|
|
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: 'clients', route: '/clients', sidebarIcon: true },
|
|
19
|
+
],
|
|
20
|
+
environments: {
|
|
21
|
+
beta: { baseUrl: 'http://localhost:3000', seed: false },
|
|
22
|
+
},
|
|
23
|
+
portals: [],
|
|
24
|
+
breakpoints: [],
|
|
25
|
+
integrations: [],
|
|
26
|
+
workers: [],
|
|
27
|
+
workflows: [],
|
|
28
|
+
accuracy: { enabled: true, 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 node:fs/promises
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const statMock = vi.fn();
|
|
48
|
+
const readdirMock = vi.fn();
|
|
49
|
+
|
|
50
|
+
vi.mock('node:fs/promises', async (importOriginal) => {
|
|
51
|
+
const actual = await importOriginal<typeof import('node:fs/promises')>();
|
|
52
|
+
return {
|
|
53
|
+
...actual,
|
|
54
|
+
stat: (...args: unknown[]) => statMock(...args),
|
|
55
|
+
readdir: (...args: unknown[]) => readdirMock(...args),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Mock navigation helpers
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
64
|
+
const navigateToSectionMock = vi.fn().mockResolvedValue(undefined);
|
|
65
|
+
|
|
66
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
67
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
68
|
+
navigateToSection: (...args: unknown[]) => navigateToSectionMock(...args),
|
|
69
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Mock screenshot helper
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
77
|
+
|
|
78
|
+
vi.mock('../../helpers/screenshot', () => ({
|
|
79
|
+
captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Mock Playwright page
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
type RouteHandler = (route: { fulfill: (opts: Record<string, unknown>) => Promise<void> }) => void | Promise<void>;
|
|
87
|
+
|
|
88
|
+
interface MockPage {
|
|
89
|
+
close: ReturnType<typeof vi.fn>;
|
|
90
|
+
goto: ReturnType<typeof vi.fn>;
|
|
91
|
+
reload: ReturnType<typeof vi.fn>;
|
|
92
|
+
isVisible: ReturnType<typeof vi.fn>;
|
|
93
|
+
evaluate: ReturnType<typeof vi.fn>;
|
|
94
|
+
route: ReturnType<typeof vi.fn>;
|
|
95
|
+
unroute: ReturnType<typeof vi.fn>;
|
|
96
|
+
waitForTimeout: ReturnType<typeof vi.fn>;
|
|
97
|
+
locator: ReturnType<typeof vi.fn>;
|
|
98
|
+
_routeHandlers: Map<string, RouteHandler>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function makeMockPage(overrides: Partial<MockPage> = {}): MockPage {
|
|
102
|
+
const routeHandlers = new Map<string, RouteHandler>();
|
|
103
|
+
|
|
104
|
+
const page: MockPage = {
|
|
105
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
106
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
107
|
+
reload: vi.fn().mockResolvedValue(undefined),
|
|
108
|
+
isVisible: vi.fn().mockResolvedValue(false),
|
|
109
|
+
evaluate: vi.fn().mockResolvedValue(false),
|
|
110
|
+
route: vi.fn().mockImplementation(async (pattern: string, handler: RouteHandler) => {
|
|
111
|
+
routeHandlers.set(pattern, handler);
|
|
112
|
+
}),
|
|
113
|
+
unroute: vi.fn().mockResolvedValue(undefined),
|
|
114
|
+
waitForTimeout: vi.fn().mockResolvedValue(undefined),
|
|
115
|
+
locator: vi.fn().mockReturnValue({
|
|
116
|
+
first: () => ({ isVisible: vi.fn().mockResolvedValue(false) }),
|
|
117
|
+
}),
|
|
118
|
+
_routeHandlers: routeHandlers,
|
|
119
|
+
...overrides,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return page;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let currentMockPage: MockPage;
|
|
126
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
127
|
+
|
|
128
|
+
vi.mock('playwright', () => ({
|
|
129
|
+
chromium: {
|
|
130
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Dynamic import AFTER mocks
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
const { UxInspectorAgent } = await import('../12-ux-inspector');
|
|
139
|
+
|
|
140
|
+
function createAgent(phase: Phase = 'beta'): InstanceType<typeof UxInspectorAgent> {
|
|
141
|
+
return new UxInspectorAgent(stubConfig, phase, '/tmp/test-run');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Tests
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
describe('UxInspectorAgent — extended coverage', () => {
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
statMock.mockReset().mockResolvedValue({});
|
|
151
|
+
readdirMock.mockReset().mockResolvedValue(['smoke-dashboard.png', 'smoke-clients.png']);
|
|
152
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
153
|
+
navigateToSectionMock.mockReset().mockResolvedValue(undefined);
|
|
154
|
+
captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
155
|
+
|
|
156
|
+
currentMockPage = makeMockPage();
|
|
157
|
+
mockBrowser = {
|
|
158
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
159
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
// No environment configured
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
describe('no environment', () => {
|
|
168
|
+
it('returns critical finding when no environment is configured', async () => {
|
|
169
|
+
const noEnvConfig: ValidatedConfig = {
|
|
170
|
+
...stubConfig,
|
|
171
|
+
environments: {},
|
|
172
|
+
};
|
|
173
|
+
const agent = new UxInspectorAgent(noEnvConfig, 'beta', '/tmp/test-run');
|
|
174
|
+
const result = await agent.run();
|
|
175
|
+
|
|
176
|
+
const finding = result.findings.find((f) => f.id === '12-no-env');
|
|
177
|
+
expect(finding).toBeDefined();
|
|
178
|
+
expect(finding!.severity).toBe('critical');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// No credentials
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
describe('no credentials', () => {
|
|
187
|
+
it('returns critical finding when no credentials configured', async () => {
|
|
188
|
+
const noCredConfig: ValidatedConfig = {
|
|
189
|
+
...stubConfig,
|
|
190
|
+
auth: { loginUrl: '/login', credentials: {} },
|
|
191
|
+
};
|
|
192
|
+
const agent = new UxInspectorAgent(noCredConfig, 'beta', '/tmp/test-run');
|
|
193
|
+
const result = await agent.run();
|
|
194
|
+
|
|
195
|
+
const finding = result.findings.find((f) => f.id === '12-no-credentials');
|
|
196
|
+
expect(finding).toBeDefined();
|
|
197
|
+
expect(finding!.severity).toBe('critical');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// -------------------------------------------------------------------------
|
|
202
|
+
// Loading states stuck
|
|
203
|
+
// -------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
describe('loading states', () => {
|
|
206
|
+
it('creates finding when a loading spinner is stuck', async () => {
|
|
207
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
208
|
+
if (selector === '[data-testid="loading-spinner"]') return Promise.resolve(true);
|
|
209
|
+
return Promise.resolve(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const agent = createAgent();
|
|
213
|
+
const result = await agent.run();
|
|
214
|
+
|
|
215
|
+
const loadingFinding = result.findings.find((f) => f.id.includes('loading-stuck'));
|
|
216
|
+
expect(loadingFinding).toBeDefined();
|
|
217
|
+
expect(loadingFinding!.type).toBe('code-bug-cosmetic');
|
|
218
|
+
expect(loadingFinding!.severity).toBe('medium');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('creates finding when skeleton loader is stuck', async () => {
|
|
222
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
223
|
+
if (selector === '.skeleton') return Promise.resolve(true);
|
|
224
|
+
return Promise.resolve(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const agent = createAgent();
|
|
228
|
+
const result = await agent.run();
|
|
229
|
+
|
|
230
|
+
const loadingFinding = result.findings.find((f) => f.id.includes('loading-stuck'));
|
|
231
|
+
expect(loadingFinding).toBeDefined();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('does not create finding when no loading indicators visible', async () => {
|
|
235
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
236
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(true); // has data rows
|
|
237
|
+
|
|
238
|
+
const agent = createAgent();
|
|
239
|
+
const result = await agent.run();
|
|
240
|
+
|
|
241
|
+
const loadingFindings = result.findings.filter((f) => f.id.includes('loading-stuck'));
|
|
242
|
+
// May still get error-boundary findings, but no loading-stuck
|
|
243
|
+
expect(loadingFindings).toHaveLength(0);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// -------------------------------------------------------------------------
|
|
248
|
+
// Form validation checks
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
describe('form validation', () => {
|
|
252
|
+
it('creates finding when form has required fields but no validation messages on submit', async () => {
|
|
253
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
254
|
+
if (selector === 'form') return Promise.resolve(true);
|
|
255
|
+
return Promise.resolve(false);
|
|
256
|
+
});
|
|
257
|
+
currentMockPage.evaluate = vi.fn().mockImplementation((fn: unknown) => {
|
|
258
|
+
// First evaluate call: check for data rows (in checkEmptyStates)
|
|
259
|
+
// Second evaluate call: check for required fields
|
|
260
|
+
if (typeof fn === 'function') {
|
|
261
|
+
return Promise.resolve(true); // has required fields or data rows
|
|
262
|
+
}
|
|
263
|
+
return Promise.resolve(true);
|
|
264
|
+
});
|
|
265
|
+
const clickMock = vi.fn().mockResolvedValue(undefined);
|
|
266
|
+
currentMockPage.locator = vi.fn().mockReturnValue({
|
|
267
|
+
first: () => ({
|
|
268
|
+
isVisible: vi.fn().mockResolvedValue(true),
|
|
269
|
+
click: clickMock,
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const agent = createAgent();
|
|
274
|
+
const result = await agent.run();
|
|
275
|
+
|
|
276
|
+
const validationFinding = result.findings.find((f) => f.id.includes('form-validation'));
|
|
277
|
+
expect(validationFinding).toBeDefined();
|
|
278
|
+
expect(validationFinding!.severity).toBe('medium');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('does NOT create finding when form validation messages are shown', async () => {
|
|
282
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
283
|
+
if (selector === 'form') return Promise.resolve(true);
|
|
284
|
+
if (selector === '[data-testid="field-error"]') return Promise.resolve(true);
|
|
285
|
+
// Error boundary selectors
|
|
286
|
+
if (selector === '[role="alert"]') return Promise.resolve(true);
|
|
287
|
+
return Promise.resolve(false);
|
|
288
|
+
});
|
|
289
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
|
|
290
|
+
currentMockPage.locator = vi.fn().mockReturnValue({
|
|
291
|
+
first: () => ({
|
|
292
|
+
isVisible: vi.fn().mockResolvedValue(true),
|
|
293
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const agent = createAgent();
|
|
298
|
+
const result = await agent.run();
|
|
299
|
+
|
|
300
|
+
const validationFindings = result.findings.filter((f) => f.id.includes('form-validation'));
|
|
301
|
+
expect(validationFindings).toHaveLength(0);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('skips form validation when no form is visible', async () => {
|
|
305
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
306
|
+
if (selector === 'form') return Promise.resolve(false);
|
|
307
|
+
return Promise.resolve(false);
|
|
308
|
+
});
|
|
309
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
|
|
310
|
+
|
|
311
|
+
const agent = createAgent();
|
|
312
|
+
const result = await agent.run();
|
|
313
|
+
|
|
314
|
+
const validationFindings = result.findings.filter((f) => f.id.includes('form-validation'));
|
|
315
|
+
expect(validationFindings).toHaveLength(0);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// -------------------------------------------------------------------------
|
|
320
|
+
// Truncated text detection
|
|
321
|
+
// -------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
describe('truncated text', () => {
|
|
324
|
+
it('creates finding when elements with truncated text are detected', async () => {
|
|
325
|
+
// evaluate is called multiple times:
|
|
326
|
+
// - checkEmptyStates (once per module) — should return true (data present)
|
|
327
|
+
// - checkTruncatedText (once per module) — should return count > 0
|
|
328
|
+
// - possibly checkFormValidation
|
|
329
|
+
// We use a counter to know which call is which: for 2 modules, calls 1-2 are empty-state,
|
|
330
|
+
// calls 3-4 (if reached) are truncated text.
|
|
331
|
+
let evalCallCount = 0;
|
|
332
|
+
currentMockPage.evaluate = vi.fn().mockImplementation(() => {
|
|
333
|
+
evalCallCount++;
|
|
334
|
+
// Calls for checkEmptyStates (2 modules): return true (data present)
|
|
335
|
+
// Calls for checkFormValidation: also calls evaluate
|
|
336
|
+
// Calls for checkTruncatedText: return count
|
|
337
|
+
// Since evaluate is used by checkEmptyStates, checkFormValidation AND checkTruncatedText
|
|
338
|
+
// we just return 3 for all — truthy means data present, and 3 means truncated elements
|
|
339
|
+
return Promise.resolve(3);
|
|
340
|
+
});
|
|
341
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
342
|
+
// Make error boundary found to avoid those findings
|
|
343
|
+
if (selector === '[role="alert"]') return Promise.resolve(true);
|
|
344
|
+
return Promise.resolve(false);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const agent = createAgent();
|
|
348
|
+
const result = await agent.run();
|
|
349
|
+
|
|
350
|
+
const truncatedFinding = result.findings.find((f) => f.id.includes('truncated-text'));
|
|
351
|
+
expect(truncatedFinding).toBeDefined();
|
|
352
|
+
expect(truncatedFinding!.severity).toBe('low');
|
|
353
|
+
expect(truncatedFinding!.description).toContain('element(s)');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('does NOT create finding when no truncated text is detected', async () => {
|
|
357
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(0);
|
|
358
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
359
|
+
if (selector === '[role="alert"]') return Promise.resolve(true);
|
|
360
|
+
return Promise.resolve(false);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const agent = createAgent();
|
|
364
|
+
const result = await agent.run();
|
|
365
|
+
|
|
366
|
+
const truncatedFindings = result.findings.filter((f) => f.id.includes('truncated-text'));
|
|
367
|
+
expect(truncatedFindings).toHaveLength(0);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// -------------------------------------------------------------------------
|
|
372
|
+
// Browser cleanup on error
|
|
373
|
+
// -------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
describe('browser cleanup', () => {
|
|
376
|
+
it('closes browser and page even when login throws', async () => {
|
|
377
|
+
loginMock.mockRejectedValue(new Error('Login failed'));
|
|
378
|
+
|
|
379
|
+
const agent = createAgent();
|
|
380
|
+
const result = await agent.run();
|
|
381
|
+
|
|
382
|
+
expect(result.status).toBe('failed');
|
|
383
|
+
expect(currentMockPage.close).toHaveBeenCalled();
|
|
384
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// -------------------------------------------------------------------------
|
|
389
|
+
// Screenshot evidence is captured
|
|
390
|
+
// -------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
describe('evidence collection', () => {
|
|
393
|
+
it('captures screenshots for loading-stuck findings', async () => {
|
|
394
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
395
|
+
if (selector === '[aria-label="Loading"]') return Promise.resolve(true);
|
|
396
|
+
return Promise.resolve(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const agent = createAgent();
|
|
400
|
+
const result = await agent.run();
|
|
401
|
+
|
|
402
|
+
const screenshotEvidence = result.evidence.filter((e) => e.type === 'screenshot');
|
|
403
|
+
expect(screenshotEvidence.length).toBeGreaterThan(0);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('captures screenshots for error-boundary findings', async () => {
|
|
407
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
408
|
+
|
|
409
|
+
const agent = createAgent();
|
|
410
|
+
const result = await agent.run();
|
|
411
|
+
|
|
412
|
+
const screenshotEvidence = result.evidence.filter((e) => e.type === 'screenshot');
|
|
413
|
+
expect(screenshotEvidence.length).toBeGreaterThan(0);
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// -------------------------------------------------------------------------
|
|
418
|
+
// Environment fallback
|
|
419
|
+
// -------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
describe('environment fallback', () => {
|
|
422
|
+
it('uses alpha environment when beta is not configured', async () => {
|
|
423
|
+
const alphaOnlyConfig: ValidatedConfig = {
|
|
424
|
+
...stubConfig,
|
|
425
|
+
environments: {
|
|
426
|
+
alpha: { baseUrl: 'http://alpha.localhost:3000', seed: false },
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const agent = new UxInspectorAgent(alphaOnlyConfig, 'beta', '/tmp/test-run');
|
|
431
|
+
const result = await agent.run();
|
|
432
|
+
|
|
433
|
+
// Should not produce a no-env finding
|
|
434
|
+
const noEnvFinding = result.findings.find((f) => f.id === '12-no-env');
|
|
435
|
+
expect(noEnvFinding).toBeUndefined();
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// -------------------------------------------------------------------------
|
|
440
|
+
// isVisible throws (error resilience)
|
|
441
|
+
// -------------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
describe('error resilience', () => {
|
|
444
|
+
it('handles isVisible throwing gracefully in loading state check', async () => {
|
|
445
|
+
currentMockPage.isVisible = vi.fn().mockRejectedValue(new Error('Detached'));
|
|
446
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
|
|
447
|
+
|
|
448
|
+
const agent = createAgent();
|
|
449
|
+
const result = await agent.run();
|
|
450
|
+
|
|
451
|
+
// Should not crash; the checks are best-effort
|
|
452
|
+
expect(result.status).toBeDefined();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('handles evaluate throwing gracefully in empty state check', async () => {
|
|
456
|
+
currentMockPage.evaluate = vi.fn().mockRejectedValue(new Error('Execution context was destroyed'));
|
|
457
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
458
|
+
|
|
459
|
+
const agent = createAgent();
|
|
460
|
+
const result = await agent.run();
|
|
461
|
+
|
|
462
|
+
expect(result.status).toBeDefined();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
});
|