@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,586 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { WorkflowConfig } from '../../core/config';
|
|
4
|
+
import type { Phase } from '../../core/types';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Stub config helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
function makeWorkflow(name: string, steps: WorkflowConfig['steps'] = []): WorkflowConfig {
|
|
11
|
+
return { name, steps };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
15
|
+
return {
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
name: 'test',
|
|
18
|
+
auth: {
|
|
19
|
+
loginUrl: 'http://localhost/login',
|
|
20
|
+
credentials: {
|
|
21
|
+
admin: { email: 'admin@test.com', password: 'secret' },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
25
|
+
environments: {
|
|
26
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
27
|
+
},
|
|
28
|
+
portals: [],
|
|
29
|
+
breakpoints: [],
|
|
30
|
+
integrations: [],
|
|
31
|
+
workers: [],
|
|
32
|
+
workflows: [
|
|
33
|
+
makeWorkflow('onboarding', [
|
|
34
|
+
{ action: 'navigate', target: '/onboarding' },
|
|
35
|
+
{ action: 'click', target: '[data-testid="next"]' },
|
|
36
|
+
{ action: 'verify', target: '[data-testid="step-2"]', contains: 'Step 2' },
|
|
37
|
+
]),
|
|
38
|
+
],
|
|
39
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
40
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
41
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
42
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
43
|
+
thresholds: {
|
|
44
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
45
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
46
|
+
apiResponseTime: 2000,
|
|
47
|
+
bundleSizeLimit: 500000,
|
|
48
|
+
maxConsoleErrors: 0,
|
|
49
|
+
},
|
|
50
|
+
disabledAgents: [],
|
|
51
|
+
disabledAgentSet: new Set<number>(),
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Mock fetch (preFlight URL reachability)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
const fetchMock = vi.fn();
|
|
61
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Mock helpers
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
68
|
+
const waitForSectionLoadMock = vi.fn().mockResolvedValue(undefined);
|
|
69
|
+
const captureScreenshotMock = vi.fn().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
70
|
+
const fillFormMock = vi.fn().mockResolvedValue(undefined);
|
|
71
|
+
|
|
72
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
73
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
74
|
+
waitForSectionLoad: (...args: unknown[]) => waitForSectionLoadMock(...args),
|
|
75
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
76
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
vi.mock('../../helpers/screenshot', () => ({
|
|
80
|
+
captureScreenshot: (...args: unknown[]) => captureScreenshotMock(...args),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
vi.mock('../../helpers/form-filler', () => ({
|
|
84
|
+
fillForm: (...args: unknown[]) => fillFormMock(...args),
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Mock Playwright
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
interface MockElement {
|
|
92
|
+
textContent: ReturnType<typeof vi.fn>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface MockPage {
|
|
96
|
+
close: ReturnType<typeof vi.fn>;
|
|
97
|
+
goto: ReturnType<typeof vi.fn>;
|
|
98
|
+
click: ReturnType<typeof vi.fn>;
|
|
99
|
+
waitForSelector: ReturnType<typeof vi.fn>;
|
|
100
|
+
setViewportSize: ReturnType<typeof vi.fn>;
|
|
101
|
+
_resolveWaitForSelector: (selector: string, resolvedText: string) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function makeMockPage(overrides: Partial<MockPage> = {}): MockPage {
|
|
105
|
+
const waitForSelectorMap = new Map<string, string>();
|
|
106
|
+
|
|
107
|
+
const page: MockPage = {
|
|
108
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
109
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
110
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
111
|
+
waitForSelector: vi.fn().mockImplementation(async (selector: string) => {
|
|
112
|
+
const text = waitForSelectorMap.get(selector) ?? '';
|
|
113
|
+
const element: MockElement = {
|
|
114
|
+
textContent: vi.fn().mockResolvedValue(text),
|
|
115
|
+
};
|
|
116
|
+
return element;
|
|
117
|
+
}),
|
|
118
|
+
setViewportSize: vi.fn().mockResolvedValue(undefined),
|
|
119
|
+
_resolveWaitForSelector(selector: string, text: string) {
|
|
120
|
+
waitForSelectorMap.set(selector, text);
|
|
121
|
+
},
|
|
122
|
+
...overrides,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return page;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let currentMockPage: MockPage;
|
|
129
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
130
|
+
|
|
131
|
+
vi.mock('playwright', async () => ({
|
|
132
|
+
chromium: {
|
|
133
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
134
|
+
},
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Dynamic import AFTER mocks are hoisted
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
const { WorkflowCompletionTesterAgent } = await import('../20-workflow-completion-tester');
|
|
142
|
+
|
|
143
|
+
function createAgent(
|
|
144
|
+
config: ValidatedConfig = makeConfig(),
|
|
145
|
+
phase: Phase = 'uat',
|
|
146
|
+
): InstanceType<typeof WorkflowCompletionTesterAgent> {
|
|
147
|
+
return new WorkflowCompletionTesterAgent(config, phase, '/tmp/test-run');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Tests
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
describe('WorkflowCompletionTesterAgent', () => {
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
157
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
158
|
+
waitForSectionLoadMock.mockReset().mockResolvedValue(undefined);
|
|
159
|
+
captureScreenshotMock.mockReset().mockResolvedValue('/tmp/test-run/screenshot.png');
|
|
160
|
+
fillFormMock.mockReset().mockResolvedValue(undefined);
|
|
161
|
+
|
|
162
|
+
currentMockPage = makeMockPage();
|
|
163
|
+
mockBrowser = {
|
|
164
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
165
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Identity
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
it('has agentId 20 and agentName "Workflow Completion Tester"', () => {
|
|
174
|
+
const agent = createAgent();
|
|
175
|
+
expect(agent.agentId).toBe(20);
|
|
176
|
+
expect(agent.agentName).toBe('Workflow Completion Tester');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// -------------------------------------------------------------------------
|
|
180
|
+
// preFlight()
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
describe('preFlight()', () => {
|
|
184
|
+
it('throws when workflows array is empty', async () => {
|
|
185
|
+
const config = makeConfig({ workflows: [] });
|
|
186
|
+
const agent = createAgent(config);
|
|
187
|
+
const result = await agent.run();
|
|
188
|
+
|
|
189
|
+
expect(result.status).toBe('failed');
|
|
190
|
+
expect(result.findings[0].description).toContain('workflow');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('resolves gracefully when base URL is not reachable (fallback)', async () => {
|
|
194
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
195
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
|
|
196
|
+
const agent = createAgent();
|
|
197
|
+
const result = await agent.run();
|
|
198
|
+
|
|
199
|
+
// With resolveEnvironment the agent no longer throws on unreachable URLs.
|
|
200
|
+
// preFlight succeeds; execute proceeds with the fallback environment.
|
|
201
|
+
expect(result.findings.some((f) => f.description.includes('not reachable'))).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('passes when workflows are configured and base URL is reachable', async () => {
|
|
205
|
+
const agent = createAgent();
|
|
206
|
+
const result = await agent.run();
|
|
207
|
+
|
|
208
|
+
expect(result.findings.some(f => f.description.includes('workflow'))).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// -------------------------------------------------------------------------
|
|
213
|
+
// Successful workflow execution
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
describe('execute() — successful workflow', () => {
|
|
217
|
+
it('returns no findings when all workflow steps succeed', async () => {
|
|
218
|
+
// Set expected text for verify step
|
|
219
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2 content');
|
|
220
|
+
|
|
221
|
+
const agent = createAgent();
|
|
222
|
+
const result = await agent.run();
|
|
223
|
+
|
|
224
|
+
expect(result.findings).toHaveLength(0);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('calls page.goto for navigate steps', async () => {
|
|
228
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
|
|
229
|
+
|
|
230
|
+
const agent = createAgent();
|
|
231
|
+
await agent.run();
|
|
232
|
+
|
|
233
|
+
expect(currentMockPage.goto).toHaveBeenCalledWith('http://localhost:3000/onboarding');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('calls page.click for click steps', async () => {
|
|
237
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
|
|
238
|
+
|
|
239
|
+
const agent = createAgent();
|
|
240
|
+
await agent.run();
|
|
241
|
+
|
|
242
|
+
expect(currentMockPage.click).toHaveBeenCalledWith('[data-testid="next"]');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('takes a screenshot at each step as evidence', async () => {
|
|
246
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2');
|
|
247
|
+
|
|
248
|
+
const agent = createAgent();
|
|
249
|
+
const result = await agent.run();
|
|
250
|
+
|
|
251
|
+
// 3 steps in onboarding workflow → 3 screenshots
|
|
252
|
+
const screenshotEvidence = result.evidence.filter(e => e.type === 'screenshot');
|
|
253
|
+
expect(screenshotEvidence.length).toBeGreaterThanOrEqual(3);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// -------------------------------------------------------------------------
|
|
258
|
+
// Failed step creates finding with workflow name
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
describe('execute() — failed step', () => {
|
|
262
|
+
it('creates a finding with the workflow name when a step fails', async () => {
|
|
263
|
+
// click step throws
|
|
264
|
+
currentMockPage.click.mockRejectedValue(new Error('Element not found'));
|
|
265
|
+
|
|
266
|
+
const agent = createAgent();
|
|
267
|
+
const result = await agent.run();
|
|
268
|
+
|
|
269
|
+
const workflowFinding = result.findings.find(
|
|
270
|
+
f => f.module.includes('onboarding'),
|
|
271
|
+
);
|
|
272
|
+
expect(workflowFinding).toBeDefined();
|
|
273
|
+
expect(workflowFinding!.description).toContain('onboarding');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('creates a finding that includes the step index', async () => {
|
|
277
|
+
currentMockPage.click.mockRejectedValue(new Error('Selector timeout'));
|
|
278
|
+
|
|
279
|
+
const agent = createAgent();
|
|
280
|
+
const result = await agent.run();
|
|
281
|
+
|
|
282
|
+
const finding = result.findings[0];
|
|
283
|
+
// Step index 1 is the click step (0-indexed)
|
|
284
|
+
expect(finding.description).toContain('step 1');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('captures a failure screenshot when a step fails', async () => {
|
|
288
|
+
currentMockPage.click.mockRejectedValue(new Error('Click failed'));
|
|
289
|
+
|
|
290
|
+
const agent = createAgent();
|
|
291
|
+
const result = await agent.run();
|
|
292
|
+
|
|
293
|
+
const finding = result.findings[0];
|
|
294
|
+
expect(finding.screenshot).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('stops processing the workflow after the first failed step', async () => {
|
|
298
|
+
// Navigate (step 0) succeeds; click (step 1) fails
|
|
299
|
+
currentMockPage.click.mockRejectedValue(new Error('Click failed'));
|
|
300
|
+
|
|
301
|
+
const agent = createAgent();
|
|
302
|
+
await agent.run();
|
|
303
|
+
|
|
304
|
+
// waitForSelector for verify (step 2) should NOT have been called
|
|
305
|
+
const verifyCall = (currentMockPage.waitForSelector.mock.calls as Array<unknown[]>).find(
|
|
306
|
+
(c) => c[0] === '[data-testid="step-2"]',
|
|
307
|
+
);
|
|
308
|
+
expect(verifyCall).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// verify action checks text content
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe('execute() — verify action', () => {
|
|
317
|
+
it('creates no finding when element contains expected text', async () => {
|
|
318
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Step 2 content here');
|
|
319
|
+
|
|
320
|
+
const agent = createAgent();
|
|
321
|
+
const result = await agent.run();
|
|
322
|
+
|
|
323
|
+
expect(result.findings).toHaveLength(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('creates a finding when element does not contain expected text', async () => {
|
|
327
|
+
// waitForSelector returns element but textContent does not match
|
|
328
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Wrong content');
|
|
329
|
+
|
|
330
|
+
const agent = createAgent();
|
|
331
|
+
const result = await agent.run();
|
|
332
|
+
|
|
333
|
+
const verifyFinding = result.findings.find(
|
|
334
|
+
f => f.description.includes('does not contain'),
|
|
335
|
+
);
|
|
336
|
+
expect(verifyFinding).toBeDefined();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('creates a finding that mentions the expected text when mismatch', async () => {
|
|
340
|
+
currentMockPage._resolveWaitForSelector('[data-testid="step-2"]', 'Something else');
|
|
341
|
+
|
|
342
|
+
const agent = createAgent();
|
|
343
|
+
const result = await agent.run();
|
|
344
|
+
|
|
345
|
+
expect(result.findings[0].description).toContain('Step 2');
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// -------------------------------------------------------------------------
|
|
350
|
+
// fill-form action
|
|
351
|
+
// -------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
describe('execute() — fill-form action', () => {
|
|
354
|
+
it('calls fillForm helper for fill-form steps', async () => {
|
|
355
|
+
const config = makeConfig({
|
|
356
|
+
workflows: [
|
|
357
|
+
{
|
|
358
|
+
name: 'create-invoice',
|
|
359
|
+
steps: [
|
|
360
|
+
{ action: 'fill-form', target: 'form[data-form="invoice"]', data: 'test-data' },
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const agent = createAgent(config);
|
|
367
|
+
await agent.run();
|
|
368
|
+
|
|
369
|
+
expect(fillFormMock).toHaveBeenCalledWith(
|
|
370
|
+
currentMockPage,
|
|
371
|
+
'form[data-form="invoice"]',
|
|
372
|
+
{ data: 'test-data' },
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// -------------------------------------------------------------------------
|
|
378
|
+
// wait action
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
describe('execute() — wait action', () => {
|
|
382
|
+
it('calls waitForSelector for wait steps', async () => {
|
|
383
|
+
const config = makeConfig({
|
|
384
|
+
workflows: [
|
|
385
|
+
{
|
|
386
|
+
name: 'loading-test',
|
|
387
|
+
steps: [
|
|
388
|
+
{ action: 'wait', target: '[data-testid="spinner-gone"]', timeout: 3000 },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const agent = createAgent(config);
|
|
395
|
+
await agent.run();
|
|
396
|
+
|
|
397
|
+
expect(currentMockPage.waitForSelector).toHaveBeenCalledWith(
|
|
398
|
+
'[data-testid="spinner-gone"]',
|
|
399
|
+
{ timeout: 3000 },
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// -------------------------------------------------------------------------
|
|
405
|
+
// Multiple workflows
|
|
406
|
+
// -------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
describe('execute() — multiple workflows', () => {
|
|
409
|
+
it('runs all workflows and accumulates findings', async () => {
|
|
410
|
+
const config = makeConfig({
|
|
411
|
+
workflows: [
|
|
412
|
+
{
|
|
413
|
+
name: 'workflow-a',
|
|
414
|
+
steps: [{ action: 'navigate', target: '/a' }],
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: 'workflow-b',
|
|
418
|
+
steps: [{ action: 'click', target: '[broken]' }],
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// navigate succeeds; click fails
|
|
424
|
+
currentMockPage.click.mockRejectedValue(new Error('Not found'));
|
|
425
|
+
|
|
426
|
+
const agent = createAgent(config);
|
|
427
|
+
const result = await agent.run();
|
|
428
|
+
|
|
429
|
+
// workflow-a passes, workflow-b fails
|
|
430
|
+
const wfBFinding = result.findings.find(f => f.module.includes('workflow-b'));
|
|
431
|
+
expect(wfBFinding).toBeDefined();
|
|
432
|
+
// workflow-a should NOT produce a finding
|
|
433
|
+
const wfAFinding = result.findings.find(f => f.module.includes('workflow-a'));
|
|
434
|
+
expect(wfAFinding).toBeUndefined();
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// -------------------------------------------------------------------------
|
|
439
|
+
// No credentials
|
|
440
|
+
// -------------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
describe('execute() — no credentials', () => {
|
|
443
|
+
it('creates a critical finding when no credentials are configured', async () => {
|
|
444
|
+
const config = makeConfig({
|
|
445
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
446
|
+
});
|
|
447
|
+
const agent = createAgent(config);
|
|
448
|
+
const result = await agent.run();
|
|
449
|
+
|
|
450
|
+
const finding = result.findings.find(f => f.id.includes('no-credentials'));
|
|
451
|
+
expect(finding).toBeDefined();
|
|
452
|
+
expect(finding!.severity).toBe('critical');
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// -------------------------------------------------------------------------
|
|
457
|
+
// Login failure
|
|
458
|
+
// -------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
describe('execute() — login failure', () => {
|
|
461
|
+
it('creates a medium finding and skips workflows when login fails', async () => {
|
|
462
|
+
loginMock.mockRejectedValue(new Error('Invalid credentials'));
|
|
463
|
+
|
|
464
|
+
const agent = createAgent();
|
|
465
|
+
const result = await agent.run();
|
|
466
|
+
|
|
467
|
+
const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
|
|
468
|
+
expect(loginFinding).toBeDefined();
|
|
469
|
+
expect(loginFinding!.severity).toBe('medium');
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// -------------------------------------------------------------------------
|
|
474
|
+
// Playwright crash
|
|
475
|
+
// -------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
describe('execute() — playwright crash', () => {
|
|
478
|
+
it('creates a medium finding when browser launch throws', async () => {
|
|
479
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Browser not found'));
|
|
480
|
+
|
|
481
|
+
const agent = createAgent();
|
|
482
|
+
const result = await agent.run();
|
|
483
|
+
|
|
484
|
+
const playwrightFinding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
485
|
+
expect(playwrightFinding).toBeDefined();
|
|
486
|
+
expect(playwrightFinding!.severity).toBe('medium');
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// -------------------------------------------------------------------------
|
|
491
|
+
// Navigate with absolute URL
|
|
492
|
+
// -------------------------------------------------------------------------
|
|
493
|
+
|
|
494
|
+
describe('execute() — navigate with http URL', () => {
|
|
495
|
+
it('uses absolute URL directly when target starts with http', async () => {
|
|
496
|
+
const config = makeConfig({
|
|
497
|
+
workflows: [
|
|
498
|
+
{
|
|
499
|
+
name: 'external-nav',
|
|
500
|
+
steps: [{ action: 'navigate', target: 'http://external.com/page' }],
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const agent = createAgent(config);
|
|
506
|
+
await agent.run();
|
|
507
|
+
|
|
508
|
+
expect(currentMockPage.goto).toHaveBeenCalledWith('http://external.com/page');
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// -------------------------------------------------------------------------
|
|
513
|
+
// Click step with verify selector
|
|
514
|
+
// -------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
describe('execute() — click with verify', () => {
|
|
517
|
+
it('waits for verify selector after click', async () => {
|
|
518
|
+
const config = makeConfig({
|
|
519
|
+
workflows: [
|
|
520
|
+
{
|
|
521
|
+
name: 'verify-after-click',
|
|
522
|
+
steps: [
|
|
523
|
+
{ action: 'click', target: '[data-testid="btn"]', verify: '[data-testid="result"]', timeout: 3000 },
|
|
524
|
+
],
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
});
|
|
528
|
+
currentMockPage._resolveWaitForSelector('[data-testid="result"]', 'Done');
|
|
529
|
+
|
|
530
|
+
const agent = createAgent(config);
|
|
531
|
+
await agent.run();
|
|
532
|
+
|
|
533
|
+
expect(currentMockPage.waitForSelector).toHaveBeenCalledWith(
|
|
534
|
+
'[data-testid="result"]',
|
|
535
|
+
{ timeout: 3000 },
|
|
536
|
+
);
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// -------------------------------------------------------------------------
|
|
541
|
+
// fill-form without data
|
|
542
|
+
// -------------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
describe('execute() — fill-form without data', () => {
|
|
545
|
+
it('calls fillForm with undefined data when step has no data', async () => {
|
|
546
|
+
const config = makeConfig({
|
|
547
|
+
workflows: [
|
|
548
|
+
{
|
|
549
|
+
name: 'empty-form',
|
|
550
|
+
steps: [{ action: 'fill-form', target: 'form' }],
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const agent = createAgent(config);
|
|
556
|
+
await agent.run();
|
|
557
|
+
|
|
558
|
+
expect(fillFormMock).toHaveBeenCalledWith(currentMockPage, 'form', undefined);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// -------------------------------------------------------------------------
|
|
563
|
+
// Screenshot capture failure during success path
|
|
564
|
+
// -------------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
describe('execute() — screenshot failure on success', () => {
|
|
567
|
+
it('does not add screenshot evidence when captureScreenshot fails', async () => {
|
|
568
|
+
captureScreenshotMock.mockResolvedValue('');
|
|
569
|
+
|
|
570
|
+
const config = makeConfig({
|
|
571
|
+
workflows: [
|
|
572
|
+
{
|
|
573
|
+
name: 'screenshot-fail',
|
|
574
|
+
steps: [{ action: 'navigate', target: '/page' }],
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const agent = createAgent(config);
|
|
580
|
+
const result = await agent.run();
|
|
581
|
+
|
|
582
|
+
// With empty string path, evidence should not be added (or be empty)
|
|
583
|
+
expect(result.findings).toHaveLength(0);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
});
|