@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,443 @@
|
|
|
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 (used in preFlight via stat/readdir)
|
|
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', () => {
|
|
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
|
+
it('has agentId 12 and agentName "UX Inspector"', () => {
|
|
164
|
+
const agent = createAgent();
|
|
165
|
+
expect(agent.agentId).toBe(12);
|
|
166
|
+
expect(agent.agentName).toBe('UX Inspector');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// preFlight()
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
describe('preFlight()', () => {
|
|
174
|
+
it('throws when run directory does not exist', async () => {
|
|
175
|
+
statMock.mockRejectedValue(new Error('ENOENT'));
|
|
176
|
+
|
|
177
|
+
const agent = createAgent();
|
|
178
|
+
const result = await agent.run();
|
|
179
|
+
|
|
180
|
+
expect(result.status).toBe('failed');
|
|
181
|
+
expect(result.findings[0].description).toContain('Run directory does not exist');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('does not throw on missing screenshots (advisory agent)', async () => {
|
|
185
|
+
statMock.mockResolvedValue({});
|
|
186
|
+
readdirMock.mockResolvedValue([]);
|
|
187
|
+
|
|
188
|
+
const agent = createAgent();
|
|
189
|
+
const result = await agent.run();
|
|
190
|
+
|
|
191
|
+
// Advisory agent — missing screenshots no longer causes preFlight failure.
|
|
192
|
+
// execute() still runs (and may produce findings from other checks), but
|
|
193
|
+
// the preFlight itself should not be the source of a "No screenshots" error.
|
|
194
|
+
const preflightScreenshotError = result.findings.find(
|
|
195
|
+
(f) => f.description.includes('No screenshots found'),
|
|
196
|
+
);
|
|
197
|
+
expect(preflightScreenshotError).toBeUndefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('passes when run directory has PNG files', async () => {
|
|
201
|
+
const agent = createAgent();
|
|
202
|
+
const result = await agent.run();
|
|
203
|
+
|
|
204
|
+
expect(result.findings.some((f) => f.description.includes('No screenshots found'))).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// -------------------------------------------------------------------------
|
|
209
|
+
// execute() — general
|
|
210
|
+
// -------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe('execute()', () => {
|
|
213
|
+
it('calls login() once before iterating modules', async () => {
|
|
214
|
+
const agent = createAgent();
|
|
215
|
+
await agent.run();
|
|
216
|
+
|
|
217
|
+
expect(loginMock).toHaveBeenCalledTimes(1);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('navigates to each configured module', async () => {
|
|
221
|
+
const agent = createAgent();
|
|
222
|
+
await agent.run();
|
|
223
|
+
|
|
224
|
+
expect(navigateToSectionMock).toHaveBeenCalledTimes(stubConfig.modules.length * 2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('returns passed status when no UX issues found', async () => {
|
|
228
|
+
// evaluate returns true (data rows present) — no empty-state finding
|
|
229
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValue(true);
|
|
230
|
+
// isVisible: error boundary IS found when API errors occur (so no finding),
|
|
231
|
+
// and no loading indicators stuck
|
|
232
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
233
|
+
// Error boundary selectors — signal that the app handles errors correctly
|
|
234
|
+
if (selector === '[data-testid="error-boundary"]' ||
|
|
235
|
+
selector === '[data-testid="error-fallback"]' ||
|
|
236
|
+
selector === '.error-boundary' ||
|
|
237
|
+
selector === '[role="alert"]') {
|
|
238
|
+
return Promise.resolve(true);
|
|
239
|
+
}
|
|
240
|
+
// Loading spinner selectors — not stuck
|
|
241
|
+
return Promise.resolve(false);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const agent = createAgent();
|
|
245
|
+
const result = await agent.run();
|
|
246
|
+
|
|
247
|
+
expect(result.status).toBe('passed');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// -------------------------------------------------------------------------
|
|
252
|
+
// ErrorBoundary check
|
|
253
|
+
// -------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
describe('API error → ErrorBoundary check', () => {
|
|
256
|
+
it('creates a finding when ErrorBoundary is NOT rendered on API error', async () => {
|
|
257
|
+
// page.isVisible always returns false — no error boundary selector matches
|
|
258
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
259
|
+
|
|
260
|
+
const agent = createAgent();
|
|
261
|
+
const result = await agent.run();
|
|
262
|
+
|
|
263
|
+
const ebFinding = result.findings.find((f) => f.id.includes('error-boundary'));
|
|
264
|
+
expect(ebFinding).toBeDefined();
|
|
265
|
+
expect(ebFinding!.severity).toBe('high');
|
|
266
|
+
expect(ebFinding!.type).toBe('code-bug-logic');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('does NOT create a finding when ErrorBoundary IS rendered', async () => {
|
|
270
|
+
// Return true for role="alert" (one of the error boundary selectors)
|
|
271
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
272
|
+
if (selector === '[role="alert"]') return Promise.resolve(true);
|
|
273
|
+
return Promise.resolve(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const agent = createAgent();
|
|
277
|
+
const result = await agent.run();
|
|
278
|
+
|
|
279
|
+
const ebFindings = result.findings.filter((f) => f.id.includes('error-boundary'));
|
|
280
|
+
expect(ebFindings).toHaveLength(0);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// -------------------------------------------------------------------------
|
|
285
|
+
// Empty state check
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
describe('empty state detection', () => {
|
|
289
|
+
it('creates a finding when data is empty and no empty-state element shown', async () => {
|
|
290
|
+
// evaluate returns false (no data rows), isVisible returns false (no empty state)
|
|
291
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValueOnce(false); // no data rows
|
|
292
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
293
|
+
|
|
294
|
+
const agent = createAgent();
|
|
295
|
+
const result = await agent.run();
|
|
296
|
+
|
|
297
|
+
const emptyFinding = result.findings.find((f) => f.id.includes('empty-state'));
|
|
298
|
+
expect(emptyFinding).toBeDefined();
|
|
299
|
+
expect(emptyFinding!.severity).toBe('low');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does NOT create a finding when empty state element is visible', async () => {
|
|
303
|
+
// evaluate returns false (no data), but empty-state selector is visible
|
|
304
|
+
currentMockPage.evaluate = vi.fn().mockResolvedValueOnce(false);
|
|
305
|
+
currentMockPage.isVisible = vi.fn().mockImplementation((selector: string) => {
|
|
306
|
+
if (selector === '[data-testid="empty-state"]') return Promise.resolve(true);
|
|
307
|
+
return Promise.resolve(false);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const agent = createAgent();
|
|
311
|
+
const result = await agent.run();
|
|
312
|
+
|
|
313
|
+
const emptyFindings = result.findings.filter((f) => f.id.includes('empty-state'));
|
|
314
|
+
expect(emptyFindings).toHaveLength(0);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// -------------------------------------------------------------------------
|
|
319
|
+
// Navigation failure
|
|
320
|
+
// -------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
describe('navigation failure', () => {
|
|
323
|
+
it('creates an infra-issue finding when module navigation throws', async () => {
|
|
324
|
+
navigateToSectionMock.mockRejectedValueOnce(new Error('Timeout navigating to dashboard'));
|
|
325
|
+
|
|
326
|
+
const agent = createAgent();
|
|
327
|
+
const result = await agent.run();
|
|
328
|
+
|
|
329
|
+
const navFinding = result.findings.find((f) => f.id.includes('nav-dashboard'));
|
|
330
|
+
expect(navFinding).toBeDefined();
|
|
331
|
+
expect(navFinding!.type).toBe('infra-issue');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// -------------------------------------------------------------------------
|
|
336
|
+
// Loading state stuck
|
|
337
|
+
// -------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
describe('loading state detection', () => {
|
|
340
|
+
it('creates a medium finding when a loading spinner is still visible', async () => {
|
|
341
|
+
currentMockPage.isVisible = vi.fn().mockImplementation(async (selector: string) => {
|
|
342
|
+
if (selector === '[data-testid="loading-spinner"]') return true;
|
|
343
|
+
return false;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const agent = createAgent();
|
|
347
|
+
const result = await agent.run();
|
|
348
|
+
|
|
349
|
+
const loadingFinding = result.findings.find(f => f.id.includes('loading-stuck'));
|
|
350
|
+
expect(loadingFinding).toBeDefined();
|
|
351
|
+
expect(loadingFinding!.severity).toBe('medium');
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// -------------------------------------------------------------------------
|
|
356
|
+
// Form validation check
|
|
357
|
+
// -------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
describe('form validation check', () => {
|
|
360
|
+
it('creates a finding when form submit does not show inline validation', async () => {
|
|
361
|
+
currentMockPage.isVisible = vi.fn().mockImplementation(async (selector: string) => {
|
|
362
|
+
if (selector === 'form') return true;
|
|
363
|
+
return false;
|
|
364
|
+
});
|
|
365
|
+
currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
|
|
366
|
+
// hasRequiredField returns true, hasDataRows returns true
|
|
367
|
+
return true;
|
|
368
|
+
});
|
|
369
|
+
currentMockPage.locator = vi.fn().mockReturnValue({
|
|
370
|
+
first: () => ({
|
|
371
|
+
isVisible: vi.fn().mockResolvedValue(true),
|
|
372
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
373
|
+
}),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const agent = createAgent();
|
|
377
|
+
const result = await agent.run();
|
|
378
|
+
|
|
379
|
+
const formFinding = result.findings.find(f => f.id.includes('form-validation'));
|
|
380
|
+
expect(formFinding).toBeDefined();
|
|
381
|
+
expect(formFinding!.severity).toBe('medium');
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// -------------------------------------------------------------------------
|
|
386
|
+
// Truncated text check
|
|
387
|
+
// -------------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
describe('truncated text detection', () => {
|
|
390
|
+
it('creates a low finding when truncated elements are detected', async () => {
|
|
391
|
+
currentMockPage.evaluate = vi.fn().mockImplementation(async () => {
|
|
392
|
+
return 3; // 3 truncated elements
|
|
393
|
+
});
|
|
394
|
+
currentMockPage.isVisible = vi.fn().mockResolvedValue(false);
|
|
395
|
+
|
|
396
|
+
const agent = createAgent();
|
|
397
|
+
const result = await agent.run();
|
|
398
|
+
|
|
399
|
+
const truncFinding = result.findings.find(f => f.id.includes('truncated-text'));
|
|
400
|
+
expect(truncFinding).toBeDefined();
|
|
401
|
+
expect(truncFinding!.severity).toBe('low');
|
|
402
|
+
expect(truncFinding!.description).toContain('3');
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// -------------------------------------------------------------------------
|
|
407
|
+
// No environment configured
|
|
408
|
+
// -------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
describe('no environment', () => {
|
|
411
|
+
it('creates a critical finding when no environment is configured', async () => {
|
|
412
|
+
const noEnvConfig: ValidatedConfig = {
|
|
413
|
+
...stubConfig,
|
|
414
|
+
environments: {},
|
|
415
|
+
};
|
|
416
|
+
const agent = new UxInspectorAgent(noEnvConfig, 'beta', '/tmp/test-run');
|
|
417
|
+
const result = await agent.run();
|
|
418
|
+
|
|
419
|
+
const finding = result.findings.find(f => f.id === '12-no-env');
|
|
420
|
+
expect(finding).toBeDefined();
|
|
421
|
+
expect(finding!.severity).toBe('critical');
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// -------------------------------------------------------------------------
|
|
426
|
+
// No credentials
|
|
427
|
+
// -------------------------------------------------------------------------
|
|
428
|
+
|
|
429
|
+
describe('no credentials', () => {
|
|
430
|
+
it('creates a critical finding when no credentials are configured', async () => {
|
|
431
|
+
const noCredConfig: ValidatedConfig = {
|
|
432
|
+
...stubConfig,
|
|
433
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
434
|
+
};
|
|
435
|
+
const agent = new UxInspectorAgent(noCredConfig, 'beta', '/tmp/test-run');
|
|
436
|
+
const result = await agent.run();
|
|
437
|
+
|
|
438
|
+
const finding = result.findings.find(f => f.id === '12-no-credentials');
|
|
439
|
+
expect(finding).toBeDefined();
|
|
440
|
+
expect(finding!.severity).toBe('critical');
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
});
|