@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,1404 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI-Powered Scenario Generation Engine
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* COMPETITIVE MOAT FEATURE - Automatic Edge Case Test Generation
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* This engine analyzes the truthpack and generates intelligent test scenarios
|
|
9
|
-
* that no human would think to test. It catches issues before they become bugs.
|
|
10
|
-
*
|
|
11
|
-
* Scenario Categories:
|
|
12
|
-
* - Auth boundary testing (role-based access, session expiry, token manipulation)
|
|
13
|
-
* - Form submission edge cases (empty, max length, special chars, XSS payloads)
|
|
14
|
-
* - API stress scenarios (concurrent requests, race conditions, retry storms)
|
|
15
|
-
* - Business logic edge cases (checkout abandonment, partial state, rollbacks)
|
|
16
|
-
* - Error recovery scenarios (network failures, timeouts, partial responses)
|
|
17
|
-
* - State machine violations (skip steps, double submit, back button attacks)
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
"use strict";
|
|
21
|
-
|
|
22
|
-
const fs = require("fs");
|
|
23
|
-
const path = require("path");
|
|
24
|
-
|
|
25
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
-
// SCENARIO TEMPLATES
|
|
27
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
-
|
|
29
|
-
const SCENARIO_TEMPLATES = {
|
|
30
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
-
// AUTHENTICATION BOUNDARY SCENARIOS
|
|
32
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
-
auth: {
|
|
34
|
-
sessionExpiry: {
|
|
35
|
-
name: "Session Expiry During Action",
|
|
36
|
-
description: "Start authenticated, let session expire mid-flow, verify graceful handling",
|
|
37
|
-
severity: "BLOCK",
|
|
38
|
-
steps: [
|
|
39
|
-
{ action: "login", params: { role: "user" } },
|
|
40
|
-
{ action: "navigate", params: { to: "{{protectedRoute}}" } },
|
|
41
|
-
{ action: "clearCookies", params: { filter: "session" } },
|
|
42
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
43
|
-
{ action: "expect", params: { redirect: "/login", or: { modal: "session-expired" } } }
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
tokenTampering: {
|
|
47
|
-
name: "JWT Token Manipulation",
|
|
48
|
-
description: "Modify JWT payload to test server-side validation",
|
|
49
|
-
severity: "BLOCK",
|
|
50
|
-
steps: [
|
|
51
|
-
{ action: "login", params: { role: "user" } },
|
|
52
|
-
{ action: "modifyToken", params: { field: "role", value: "admin" } },
|
|
53
|
-
{ action: "navigate", params: { to: "{{adminRoute}}" } },
|
|
54
|
-
{ action: "expect", params: { status: 403, or: { redirect: "/unauthorized" } } }
|
|
55
|
-
]
|
|
56
|
-
},
|
|
57
|
-
roleEscalation: {
|
|
58
|
-
name: "Horizontal Privilege Escalation",
|
|
59
|
-
description: "Access another user's resources by ID manipulation",
|
|
60
|
-
severity: "BLOCK",
|
|
61
|
-
steps: [
|
|
62
|
-
{ action: "login", params: { role: "user", id: "user-1" } },
|
|
63
|
-
{ action: "navigate", params: { to: "{{resourceRoute}}", params: { id: "user-2-resource" } } },
|
|
64
|
-
{ action: "expect", params: { status: 403, or: { empty: true } } }
|
|
65
|
-
]
|
|
66
|
-
},
|
|
67
|
-
concurrentSessions: {
|
|
68
|
-
name: "Concurrent Session Handling",
|
|
69
|
-
description: "Test behavior when same user logs in from multiple sessions",
|
|
70
|
-
severity: "WARN",
|
|
71
|
-
steps: [
|
|
72
|
-
{ action: "login", params: { role: "user", session: "A" } },
|
|
73
|
-
{ action: "login", params: { role: "user", session: "B" } },
|
|
74
|
-
{ action: "switchSession", params: { to: "A" } },
|
|
75
|
-
{ action: "navigate", params: { to: "{{protectedRoute}}" } },
|
|
76
|
-
{ action: "expect", params: { valid: true, or: { invalidated: true } } }
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
-
// FORM SUBMISSION EDGE CASES
|
|
83
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
-
forms: {
|
|
85
|
-
emptySubmit: {
|
|
86
|
-
name: "Empty Form Submission",
|
|
87
|
-
description: "Submit form with all fields empty",
|
|
88
|
-
severity: "WARN",
|
|
89
|
-
steps: [
|
|
90
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
91
|
-
{ action: "clearAll", params: { selector: "form input" } },
|
|
92
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
93
|
-
{ action: "expect", params: { validation: true, noServerError: true } }
|
|
94
|
-
]
|
|
95
|
-
},
|
|
96
|
-
maxLength: {
|
|
97
|
-
name: "Maximum Length Boundary",
|
|
98
|
-
description: "Submit fields at and beyond maximum length",
|
|
99
|
-
severity: "WARN",
|
|
100
|
-
steps: [
|
|
101
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
102
|
-
{ action: "fill", params: { selector: "{{textField}}", value: "A".repeat(10001) } },
|
|
103
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
104
|
-
{ action: "expect", params: { truncated: true, or: { validation: "max length" } } }
|
|
105
|
-
]
|
|
106
|
-
},
|
|
107
|
-
xssPayloads: {
|
|
108
|
-
name: "XSS Payload Injection",
|
|
109
|
-
description: "Inject script tags and event handlers",
|
|
110
|
-
severity: "BLOCK",
|
|
111
|
-
payloads: [
|
|
112
|
-
"<script>alert('xss')</script>",
|
|
113
|
-
"<img src=x onerror=alert('xss')>",
|
|
114
|
-
"javascript:alert('xss')",
|
|
115
|
-
"{{constructor.constructor('alert(1)')()}}"
|
|
116
|
-
],
|
|
117
|
-
steps: [
|
|
118
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
119
|
-
{ action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
|
|
120
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
121
|
-
{ action: "expect", params: { sanitized: true, noExecution: true } }
|
|
122
|
-
]
|
|
123
|
-
},
|
|
124
|
-
sqlInjection: {
|
|
125
|
-
name: "SQL Injection Patterns",
|
|
126
|
-
description: "Test SQL injection in form fields",
|
|
127
|
-
severity: "BLOCK",
|
|
128
|
-
payloads: [
|
|
129
|
-
"'; DROP TABLE users; --",
|
|
130
|
-
"1' OR '1'='1",
|
|
131
|
-
"UNION SELECT * FROM users",
|
|
132
|
-
"admin'--"
|
|
133
|
-
],
|
|
134
|
-
steps: [
|
|
135
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
136
|
-
{ action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
|
|
137
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
138
|
-
{ action: "expect", params: { escaped: true, noDbError: true } }
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
unicodeBomb: {
|
|
142
|
-
name: "Unicode and Special Characters",
|
|
143
|
-
description: "Test handling of unicode, emojis, RTL, zero-width chars",
|
|
144
|
-
severity: "WARN",
|
|
145
|
-
payloads: [
|
|
146
|
-
"🔥💀👾🎮", // Emojis
|
|
147
|
-
"مرحبا بالعالم", // Arabic RTL
|
|
148
|
-
"test\u0000null", // Null byte
|
|
149
|
-
"test\u200Bhidden", // Zero-width space
|
|
150
|
-
"Ṱ̈́͑ḛ̏̀s̞̿t̰̊̏", // Zalgo text
|
|
151
|
-
"test\r\ninjection" // CRLF
|
|
152
|
-
],
|
|
153
|
-
steps: [
|
|
154
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
155
|
-
{ action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
|
|
156
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
157
|
-
{ action: "expect", params: { handled: true, noCorruption: true } }
|
|
158
|
-
]
|
|
159
|
-
},
|
|
160
|
-
doubleSubmit: {
|
|
161
|
-
name: "Double Submit Prevention",
|
|
162
|
-
description: "Rapidly click submit twice to test idempotency",
|
|
163
|
-
severity: "BLOCK",
|
|
164
|
-
steps: [
|
|
165
|
-
{ action: "navigate", params: { to: "{{formRoute}}" } },
|
|
166
|
-
{ action: "fillValid", params: { form: "{{formSelector}}" } },
|
|
167
|
-
{ action: "doubleClick", params: { selector: "{{submitButton}}", delay: 50 } },
|
|
168
|
-
{ action: "expect", params: { singleSubmission: true, or: { buttonDisabled: true } } }
|
|
169
|
-
]
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
174
|
-
// API STRESS SCENARIOS
|
|
175
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
176
|
-
api: {
|
|
177
|
-
raceCondition: {
|
|
178
|
-
name: "Race Condition Detection",
|
|
179
|
-
description: "Concurrent identical requests to detect race conditions",
|
|
180
|
-
severity: "BLOCK",
|
|
181
|
-
steps: [
|
|
182
|
-
{ action: "login", params: { role: "user" } },
|
|
183
|
-
{ action: "parallel", params: {
|
|
184
|
-
count: 5,
|
|
185
|
-
action: "apiCall",
|
|
186
|
-
endpoint: "{{criticalEndpoint}}",
|
|
187
|
-
method: "POST",
|
|
188
|
-
body: "{{validPayload}}"
|
|
189
|
-
}},
|
|
190
|
-
{ action: "expect", params: {
|
|
191
|
-
idempotent: true,
|
|
192
|
-
or: { exactlyOne: true },
|
|
193
|
-
noDataCorruption: true
|
|
194
|
-
}}
|
|
195
|
-
]
|
|
196
|
-
},
|
|
197
|
-
retryStorm: {
|
|
198
|
-
name: "Retry Storm Handling",
|
|
199
|
-
description: "Simulate client retry behavior on timeout",
|
|
200
|
-
severity: "WARN",
|
|
201
|
-
steps: [
|
|
202
|
-
{ action: "interceptNetwork", params: { pattern: "{{apiPattern}}", delay: 10000 } },
|
|
203
|
-
{ action: "navigate", params: { to: "{{pageWithApi}}" } },
|
|
204
|
-
{ action: "wait", params: { ms: 2000 } },
|
|
205
|
-
{ action: "releaseNetwork" },
|
|
206
|
-
{ action: "expect", params: { noMultipleRequests: true, gracefulTimeout: true } }
|
|
207
|
-
]
|
|
208
|
-
},
|
|
209
|
-
rateLimiting: {
|
|
210
|
-
name: "Rate Limit Behavior",
|
|
211
|
-
description: "Verify rate limiting is enforced and user-friendly",
|
|
212
|
-
severity: "WARN",
|
|
213
|
-
steps: [
|
|
214
|
-
{ action: "loop", params: { count: 100, delay: 10 }, action: {
|
|
215
|
-
action: "apiCall",
|
|
216
|
-
endpoint: "{{rateLimitedEndpoint}}",
|
|
217
|
-
method: "GET"
|
|
218
|
-
}},
|
|
219
|
-
{ action: "expect", params: {
|
|
220
|
-
rateLimited: true,
|
|
221
|
-
status: 429,
|
|
222
|
-
retryAfter: "present",
|
|
223
|
-
userMessage: "present"
|
|
224
|
-
}}
|
|
225
|
-
]
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
-
// BUSINESS LOGIC EDGE CASES
|
|
231
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
232
|
-
business: {
|
|
233
|
-
checkoutAbandonment: {
|
|
234
|
-
name: "Checkout Flow Abandonment",
|
|
235
|
-
description: "Start checkout, abandon, return - verify cart state",
|
|
236
|
-
severity: "WARN",
|
|
237
|
-
steps: [
|
|
238
|
-
{ action: "addToCart", params: { product: "{{testProduct}}" } },
|
|
239
|
-
{ action: "navigate", params: { to: "/checkout" } },
|
|
240
|
-
{ action: "fillStep", params: { step: "shipping" } },
|
|
241
|
-
{ action: "navigate", params: { to: "/" } },
|
|
242
|
-
{ action: "wait", params: { ms: 5000 } },
|
|
243
|
-
{ action: "navigate", params: { to: "/checkout" } },
|
|
244
|
-
{ action: "expect", params: { cartPreserved: true, stepPreserved: true } }
|
|
245
|
-
]
|
|
246
|
-
},
|
|
247
|
-
priceManipulation: {
|
|
248
|
-
name: "Price Manipulation Attack",
|
|
249
|
-
description: "Attempt to modify prices client-side",
|
|
250
|
-
severity: "BLOCK",
|
|
251
|
-
steps: [
|
|
252
|
-
{ action: "addToCart", params: { product: "{{testProduct}}" } },
|
|
253
|
-
{ action: "interceptRequest", params: {
|
|
254
|
-
pattern: "/api/checkout",
|
|
255
|
-
modify: { "items[0].price": 0.01 }
|
|
256
|
-
}},
|
|
257
|
-
{ action: "submitCheckout" },
|
|
258
|
-
{ action: "expect", params: {
|
|
259
|
-
serverValidation: true,
|
|
260
|
-
priceFromServer: true,
|
|
261
|
-
noDiscount: true
|
|
262
|
-
}}
|
|
263
|
-
]
|
|
264
|
-
},
|
|
265
|
-
inventoryRace: {
|
|
266
|
-
name: "Inventory Race Condition",
|
|
267
|
-
description: "Two users checkout last item simultaneously",
|
|
268
|
-
severity: "BLOCK",
|
|
269
|
-
steps: [
|
|
270
|
-
{ action: "setInventory", params: { product: "{{testProduct}}", count: 1 } },
|
|
271
|
-
{ action: "parallel", params: { sessions: ["A", "B"] }, steps: [
|
|
272
|
-
{ action: "addToCart", params: { product: "{{testProduct}}" } },
|
|
273
|
-
{ action: "submitCheckout" }
|
|
274
|
-
]},
|
|
275
|
-
{ action: "expect", params: {
|
|
276
|
-
exactlyOneSuccess: true,
|
|
277
|
-
otherFails: "out of stock",
|
|
278
|
-
noOversell: true
|
|
279
|
-
}}
|
|
280
|
-
]
|
|
281
|
-
},
|
|
282
|
-
negativeQuantity: {
|
|
283
|
-
name: "Negative Quantity Attack",
|
|
284
|
-
description: "Attempt to add negative quantities",
|
|
285
|
-
severity: "BLOCK",
|
|
286
|
-
steps: [
|
|
287
|
-
{ action: "interceptRequest", params: {
|
|
288
|
-
pattern: "/api/cart",
|
|
289
|
-
modify: { quantity: -5 }
|
|
290
|
-
}},
|
|
291
|
-
{ action: "expect", params: { rejected: true, noCredit: true } }
|
|
292
|
-
]
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
297
|
-
// ERROR RECOVERY SCENARIOS
|
|
298
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
299
|
-
errors: {
|
|
300
|
-
networkDrop: {
|
|
301
|
-
name: "Network Drop Mid-Action",
|
|
302
|
-
description: "Cut network during API call, verify recovery",
|
|
303
|
-
severity: "WARN",
|
|
304
|
-
steps: [
|
|
305
|
-
{ action: "navigate", params: { to: "{{pageWithForm}}" } },
|
|
306
|
-
{ action: "fillValid", params: { form: "{{formSelector}}" } },
|
|
307
|
-
{ action: "goOffline" },
|
|
308
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
309
|
-
{ action: "expect", params: {
|
|
310
|
-
offlineMessage: true,
|
|
311
|
-
dataNotLost: true,
|
|
312
|
-
retryOption: true
|
|
313
|
-
}},
|
|
314
|
-
{ action: "goOnline" },
|
|
315
|
-
{ action: "click", params: { selector: "{{retryButton}}" } },
|
|
316
|
-
{ action: "expect", params: { success: true } }
|
|
317
|
-
]
|
|
318
|
-
},
|
|
319
|
-
partialResponse: {
|
|
320
|
-
name: "Partial Response Handling",
|
|
321
|
-
description: "Server returns truncated JSON",
|
|
322
|
-
severity: "WARN",
|
|
323
|
-
steps: [
|
|
324
|
-
{ action: "interceptResponse", params: {
|
|
325
|
-
pattern: "{{apiPattern}}",
|
|
326
|
-
truncate: 0.5 // Cut response at 50%
|
|
327
|
-
}},
|
|
328
|
-
{ action: "navigate", params: { to: "{{pageWithApi}}" } },
|
|
329
|
-
{ action: "expect", params: {
|
|
330
|
-
gracefulError: true,
|
|
331
|
-
noJsCrash: true,
|
|
332
|
-
retryOption: true
|
|
333
|
-
}}
|
|
334
|
-
]
|
|
335
|
-
},
|
|
336
|
-
serverError: {
|
|
337
|
-
name: "500 Error Recovery",
|
|
338
|
-
description: "Server returns 500, verify user-friendly handling",
|
|
339
|
-
severity: "WARN",
|
|
340
|
-
steps: [
|
|
341
|
-
{ action: "interceptResponse", params: {
|
|
342
|
-
pattern: "{{apiPattern}}",
|
|
343
|
-
status: 500,
|
|
344
|
-
body: "Internal Server Error"
|
|
345
|
-
}},
|
|
346
|
-
{ action: "navigate", params: { to: "{{pageWithApi}}" } },
|
|
347
|
-
{ action: "expect", params: {
|
|
348
|
-
userFriendlyError: true,
|
|
349
|
-
noStackTrace: true,
|
|
350
|
-
reportOption: true
|
|
351
|
-
}}
|
|
352
|
-
]
|
|
353
|
-
}
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
|
-
// STATE MACHINE VIOLATIONS
|
|
358
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
-
stateMachine: {
|
|
360
|
-
skipStep: {
|
|
361
|
-
name: "Wizard Step Skip Attack",
|
|
362
|
-
description: "Try to skip steps in multi-step flow",
|
|
363
|
-
severity: "BLOCK",
|
|
364
|
-
steps: [
|
|
365
|
-
{ action: "navigate", params: { to: "{{wizardStep1}}" } },
|
|
366
|
-
{ action: "navigate", params: { to: "{{wizardStep3}}" } }, // Skip step 2
|
|
367
|
-
{ action: "expect", params: {
|
|
368
|
-
redirectToStep: 2,
|
|
369
|
-
or: { blocked: true },
|
|
370
|
-
noIncompleteSubmit: true
|
|
371
|
-
}}
|
|
372
|
-
]
|
|
373
|
-
},
|
|
374
|
-
backButtonAbuse: {
|
|
375
|
-
name: "Back Button State Corruption",
|
|
376
|
-
description: "Use back button after completing flow",
|
|
377
|
-
severity: "WARN",
|
|
378
|
-
steps: [
|
|
379
|
-
{ action: "completeFlow", params: { flow: "{{checkoutFlow}}" } },
|
|
380
|
-
{ action: "goBack" },
|
|
381
|
-
{ action: "goBack" },
|
|
382
|
-
{ action: "click", params: { selector: "{{submitButton}}" } },
|
|
383
|
-
{ action: "expect", params: {
|
|
384
|
-
noDuplicate: true,
|
|
385
|
-
or: { alreadyCompleted: true }
|
|
386
|
-
}}
|
|
387
|
-
]
|
|
388
|
-
},
|
|
389
|
-
directUrlAccess: {
|
|
390
|
-
name: "Direct URL to Protected State",
|
|
391
|
-
description: "Access completion page without going through flow",
|
|
392
|
-
severity: "BLOCK",
|
|
393
|
-
steps: [
|
|
394
|
-
{ action: "navigate", params: { to: "{{confirmationPage}}" } },
|
|
395
|
-
{ action: "expect", params: {
|
|
396
|
-
redirect: "{{startPage}}",
|
|
397
|
-
or: { error: "invalid state" },
|
|
398
|
-
noFakeSuccess: true
|
|
399
|
-
}}
|
|
400
|
-
]
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
405
|
-
// PERFORMANCE & RESOURCE ABUSE
|
|
406
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
407
|
-
performance: {
|
|
408
|
-
largeFileUpload: {
|
|
409
|
-
name: "Oversized File Upload",
|
|
410
|
-
description: "Upload file exceeding limit",
|
|
411
|
-
severity: "WARN",
|
|
412
|
-
steps: [
|
|
413
|
-
{ action: "navigate", params: { to: "{{uploadPage}}" } },
|
|
414
|
-
{ action: "uploadFile", params: { size: "100MB", type: "image/png" } },
|
|
415
|
-
{ action: "expect", params: {
|
|
416
|
-
clientValidation: true,
|
|
417
|
-
or: { serverReject: true },
|
|
418
|
-
noHang: true,
|
|
419
|
-
maxWait: 5000
|
|
420
|
-
}}
|
|
421
|
-
]
|
|
422
|
-
},
|
|
423
|
-
infiniteScroll: {
|
|
424
|
-
name: "Infinite Scroll Memory Leak",
|
|
425
|
-
description: "Scroll to load many pages, check memory",
|
|
426
|
-
severity: "WARN",
|
|
427
|
-
steps: [
|
|
428
|
-
{ action: "navigate", params: { to: "{{infiniteScrollPage}}" } },
|
|
429
|
-
{ action: "recordMemory", params: { label: "start" } },
|
|
430
|
-
{ action: "scrollToBottom", params: { times: 50 } },
|
|
431
|
-
{ action: "recordMemory", params: { label: "end" } },
|
|
432
|
-
{ action: "expect", params: {
|
|
433
|
-
memoryGrowth: { max: "3x" },
|
|
434
|
-
noFreeze: true
|
|
435
|
-
}}
|
|
436
|
-
]
|
|
437
|
-
},
|
|
438
|
-
fileTypeSpoofing: {
|
|
439
|
-
name: "File Type Spoofing",
|
|
440
|
-
description: "Upload executable with image extension",
|
|
441
|
-
severity: "BLOCK",
|
|
442
|
-
steps: [
|
|
443
|
-
{ action: "navigate", params: { to: "{{uploadPage}}" } },
|
|
444
|
-
{ action: "uploadSpoofedFile", params: {
|
|
445
|
-
realType: "application/x-executable",
|
|
446
|
-
fakeExtension: ".png",
|
|
447
|
-
fakeMime: "image/png"
|
|
448
|
-
}},
|
|
449
|
-
{ action: "expect", params: {
|
|
450
|
-
rejected: true,
|
|
451
|
-
magicByteCheck: true
|
|
452
|
-
}}
|
|
453
|
-
]
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
459
|
-
// SCENARIO GENERATOR CLASS
|
|
460
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
461
|
-
|
|
462
|
-
class ScenarioGenerator {
|
|
463
|
-
constructor(options = {}) {
|
|
464
|
-
this.truthpackPath = options.truthpackPath || ".vibecheck/truth/truthpack.json";
|
|
465
|
-
this.maxScenariosPerCategory = options.maxScenariosPerCategory || 5;
|
|
466
|
-
this.severityFilter = options.severityFilter || ["BLOCK", "WARN"];
|
|
467
|
-
this.categories = options.categories || Object.keys(SCENARIO_TEMPLATES);
|
|
468
|
-
this.customScenarios = options.customScenarios || [];
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Load and analyze the truthpack to understand the app
|
|
473
|
-
*/
|
|
474
|
-
loadTruthpack(projectRoot) {
|
|
475
|
-
const truthpackFile = path.join(projectRoot, this.truthpackPath);
|
|
476
|
-
|
|
477
|
-
if (!fs.existsSync(truthpackFile)) {
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
return JSON.parse(fs.readFileSync(truthpackFile, "utf8"));
|
|
483
|
-
} catch {
|
|
484
|
-
return null;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Extract testable features from truthpack
|
|
490
|
-
*/
|
|
491
|
-
analyzeAppStructure(truthpack) {
|
|
492
|
-
const analysis = {
|
|
493
|
-
routes: {
|
|
494
|
-
public: [],
|
|
495
|
-
protected: [],
|
|
496
|
-
admin: [],
|
|
497
|
-
api: [],
|
|
498
|
-
forms: [],
|
|
499
|
-
uploads: [],
|
|
500
|
-
checkout: [],
|
|
501
|
-
wizard: []
|
|
502
|
-
},
|
|
503
|
-
auth: {
|
|
504
|
-
hasAuth: false,
|
|
505
|
-
roles: [],
|
|
506
|
-
sessionType: null,
|
|
507
|
-
protectedPatterns: []
|
|
508
|
-
},
|
|
509
|
-
forms: [],
|
|
510
|
-
apis: {
|
|
511
|
-
endpoints: [],
|
|
512
|
-
criticalEndpoints: [],
|
|
513
|
-
rateLimited: []
|
|
514
|
-
},
|
|
515
|
-
features: {
|
|
516
|
-
hasCheckout: false,
|
|
517
|
-
hasUpload: false,
|
|
518
|
-
hasWizard: false,
|
|
519
|
-
hasInfiniteScroll: false,
|
|
520
|
-
hasRealtime: false
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
if (!truthpack) return analysis;
|
|
525
|
-
|
|
526
|
-
// Analyze routes
|
|
527
|
-
if (truthpack.routes) {
|
|
528
|
-
const routes = truthpack.routes.serverRoutes || truthpack.routes.routes || [];
|
|
529
|
-
|
|
530
|
-
for (const route of routes) {
|
|
531
|
-
const routePath = route.path || route;
|
|
532
|
-
|
|
533
|
-
// Categorize routes
|
|
534
|
-
if (/\/api\//i.test(routePath)) {
|
|
535
|
-
analysis.routes.api.push(routePath);
|
|
536
|
-
|
|
537
|
-
// Detect critical endpoints
|
|
538
|
-
if (/checkout|payment|order|billing|transfer/i.test(routePath)) {
|
|
539
|
-
analysis.apis.criticalEndpoints.push(routePath);
|
|
540
|
-
analysis.features.hasCheckout = true;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (/admin|dashboard|manage/i.test(routePath)) {
|
|
545
|
-
analysis.routes.admin.push(routePath);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (/upload|file|image|document/i.test(routePath)) {
|
|
549
|
-
analysis.routes.uploads.push(routePath);
|
|
550
|
-
analysis.features.hasUpload = true;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (/wizard|step|onboard/i.test(routePath)) {
|
|
554
|
-
analysis.routes.wizard.push(routePath);
|
|
555
|
-
analysis.features.hasWizard = true;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
if (/form|submit|create|edit|new/i.test(routePath)) {
|
|
559
|
-
analysis.routes.forms.push(routePath);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Analyze auth
|
|
565
|
-
if (truthpack.auth) {
|
|
566
|
-
analysis.auth.hasAuth = true;
|
|
567
|
-
analysis.auth.roles = truthpack.auth.roles || [];
|
|
568
|
-
analysis.auth.protectedPatterns = truthpack.auth.protectedPatterns ||
|
|
569
|
-
truthpack.auth.nextMatcherPatterns || [];
|
|
570
|
-
|
|
571
|
-
// Detect session type
|
|
572
|
-
if (truthpack.auth.jwt || truthpack.auth.tokenBased) {
|
|
573
|
-
analysis.auth.sessionType = "jwt";
|
|
574
|
-
} else if (truthpack.auth.sessionBased) {
|
|
575
|
-
analysis.auth.sessionType = "session";
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Analyze env for feature detection
|
|
580
|
-
if (truthpack.env) {
|
|
581
|
-
const envVars = Object.keys(truthpack.env.declared || {});
|
|
582
|
-
|
|
583
|
-
if (envVars.some(v => /stripe|payment|billing/i.test(v))) {
|
|
584
|
-
analysis.features.hasCheckout = true;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (envVars.some(v => /s3|cloudinary|upload/i.test(v))) {
|
|
588
|
-
analysis.features.hasUpload = true;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (envVars.some(v => /pusher|socket|realtime/i.test(v))) {
|
|
592
|
-
analysis.features.hasRealtime = true;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return analysis;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Generate scenarios based on app analysis
|
|
601
|
-
*/
|
|
602
|
-
generateScenarios(projectRoot) {
|
|
603
|
-
const truthpack = this.loadTruthpack(projectRoot);
|
|
604
|
-
const analysis = this.analyzeAppStructure(truthpack);
|
|
605
|
-
const scenarios = [];
|
|
606
|
-
|
|
607
|
-
// Generate auth scenarios if app has auth
|
|
608
|
-
if (analysis.auth.hasAuth && this.categories.includes("auth")) {
|
|
609
|
-
scenarios.push(...this.generateAuthScenarios(analysis));
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Generate form scenarios if app has forms
|
|
613
|
-
if (analysis.routes.forms.length > 0 && this.categories.includes("forms")) {
|
|
614
|
-
scenarios.push(...this.generateFormScenarios(analysis));
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Generate API scenarios if app has APIs
|
|
618
|
-
if (analysis.routes.api.length > 0 && this.categories.includes("api")) {
|
|
619
|
-
scenarios.push(...this.generateApiScenarios(analysis));
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Generate business logic scenarios if app has checkout
|
|
623
|
-
if (analysis.features.hasCheckout && this.categories.includes("business")) {
|
|
624
|
-
scenarios.push(...this.generateBusinessScenarios(analysis));
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Generate error scenarios
|
|
628
|
-
if (this.categories.includes("errors")) {
|
|
629
|
-
scenarios.push(...this.generateErrorScenarios(analysis));
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// Generate state machine scenarios if app has wizards
|
|
633
|
-
if (analysis.features.hasWizard && this.categories.includes("stateMachine")) {
|
|
634
|
-
scenarios.push(...this.generateStateMachineScenarios(analysis));
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Generate performance scenarios
|
|
638
|
-
if (this.categories.includes("performance")) {
|
|
639
|
-
scenarios.push(...this.generatePerformanceScenarios(analysis));
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Add custom scenarios
|
|
643
|
-
scenarios.push(...this.customScenarios);
|
|
644
|
-
|
|
645
|
-
// Filter by severity
|
|
646
|
-
const filtered = scenarios.filter(s =>
|
|
647
|
-
this.severityFilter.includes(s.severity)
|
|
648
|
-
);
|
|
649
|
-
|
|
650
|
-
// Deduplicate and prioritize
|
|
651
|
-
return this.prioritizeScenarios(filtered);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Generate auth-related scenarios
|
|
656
|
-
*/
|
|
657
|
-
generateAuthScenarios(analysis) {
|
|
658
|
-
const scenarios = [];
|
|
659
|
-
const templates = SCENARIO_TEMPLATES.auth;
|
|
660
|
-
|
|
661
|
-
// Session expiry scenario
|
|
662
|
-
if (analysis.auth.sessionType) {
|
|
663
|
-
const protectedRoute = analysis.routes.protected[0] ||
|
|
664
|
-
analysis.routes.admin[0] ||
|
|
665
|
-
"/dashboard";
|
|
666
|
-
|
|
667
|
-
scenarios.push({
|
|
668
|
-
...templates.sessionExpiry,
|
|
669
|
-
category: "auth",
|
|
670
|
-
variables: {
|
|
671
|
-
protectedRoute,
|
|
672
|
-
submitButton: "[type='submit'], button:has-text('Save'), button:has-text('Submit')"
|
|
673
|
-
},
|
|
674
|
-
confidence: 0.9,
|
|
675
|
-
generated: true
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// Token tampering scenario (for JWT auth)
|
|
680
|
-
if (analysis.auth.sessionType === "jwt") {
|
|
681
|
-
const adminRoute = analysis.routes.admin[0] || "/admin";
|
|
682
|
-
|
|
683
|
-
scenarios.push({
|
|
684
|
-
...templates.tokenTampering,
|
|
685
|
-
category: "auth",
|
|
686
|
-
variables: { adminRoute },
|
|
687
|
-
confidence: 0.95,
|
|
688
|
-
generated: true
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Role escalation scenario
|
|
693
|
-
if (analysis.auth.roles.length > 1) {
|
|
694
|
-
const resourceRoute = analysis.routes.api.find(r =>
|
|
695
|
-
/user|profile|account|resource/i.test(r)
|
|
696
|
-
) || "/api/users/:id";
|
|
697
|
-
|
|
698
|
-
scenarios.push({
|
|
699
|
-
...templates.roleEscalation,
|
|
700
|
-
category: "auth",
|
|
701
|
-
variables: { resourceRoute },
|
|
702
|
-
confidence: 0.9,
|
|
703
|
-
generated: true
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Concurrent sessions
|
|
708
|
-
scenarios.push({
|
|
709
|
-
...templates.concurrentSessions,
|
|
710
|
-
category: "auth",
|
|
711
|
-
variables: {
|
|
712
|
-
protectedRoute: analysis.routes.protected[0] || "/dashboard"
|
|
713
|
-
},
|
|
714
|
-
confidence: 0.8,
|
|
715
|
-
generated: true
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Generate form-related scenarios
|
|
723
|
-
*/
|
|
724
|
-
generateFormScenarios(analysis) {
|
|
725
|
-
const scenarios = [];
|
|
726
|
-
const templates = SCENARIO_TEMPLATES.forms;
|
|
727
|
-
const formRoutes = analysis.routes.forms;
|
|
728
|
-
|
|
729
|
-
if (formRoutes.length === 0) return scenarios;
|
|
730
|
-
|
|
731
|
-
const primaryForm = formRoutes[0];
|
|
732
|
-
|
|
733
|
-
// Empty submit
|
|
734
|
-
scenarios.push({
|
|
735
|
-
...templates.emptySubmit,
|
|
736
|
-
category: "forms",
|
|
737
|
-
variables: {
|
|
738
|
-
formRoute: primaryForm,
|
|
739
|
-
submitButton: "[type='submit']"
|
|
740
|
-
},
|
|
741
|
-
confidence: 0.95,
|
|
742
|
-
generated: true
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
// Max length
|
|
746
|
-
scenarios.push({
|
|
747
|
-
...templates.maxLength,
|
|
748
|
-
category: "forms",
|
|
749
|
-
variables: {
|
|
750
|
-
formRoute: primaryForm,
|
|
751
|
-
textField: "input[type='text'], textarea",
|
|
752
|
-
submitButton: "[type='submit']"
|
|
753
|
-
},
|
|
754
|
-
confidence: 0.85,
|
|
755
|
-
generated: true
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
// XSS payloads - generate one per payload
|
|
759
|
-
for (const payload of templates.xssPayloads.payloads.slice(0, 2)) {
|
|
760
|
-
scenarios.push({
|
|
761
|
-
...templates.xssPayloads,
|
|
762
|
-
category: "forms",
|
|
763
|
-
variables: {
|
|
764
|
-
formRoute: primaryForm,
|
|
765
|
-
textField: "input[type='text'], textarea",
|
|
766
|
-
submitButton: "[type='submit']",
|
|
767
|
-
payload
|
|
768
|
-
},
|
|
769
|
-
confidence: 0.95,
|
|
770
|
-
generated: true
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Double submit
|
|
775
|
-
scenarios.push({
|
|
776
|
-
...templates.doubleSubmit,
|
|
777
|
-
category: "forms",
|
|
778
|
-
variables: {
|
|
779
|
-
formRoute: primaryForm,
|
|
780
|
-
formSelector: "form",
|
|
781
|
-
submitButton: "[type='submit']"
|
|
782
|
-
},
|
|
783
|
-
confidence: 0.9,
|
|
784
|
-
generated: true
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* Generate API-related scenarios
|
|
792
|
-
*/
|
|
793
|
-
generateApiScenarios(analysis) {
|
|
794
|
-
const scenarios = [];
|
|
795
|
-
const templates = SCENARIO_TEMPLATES.api;
|
|
796
|
-
|
|
797
|
-
// Race condition for critical endpoints
|
|
798
|
-
if (analysis.apis.criticalEndpoints.length > 0) {
|
|
799
|
-
scenarios.push({
|
|
800
|
-
...templates.raceCondition,
|
|
801
|
-
category: "api",
|
|
802
|
-
variables: {
|
|
803
|
-
criticalEndpoint: analysis.apis.criticalEndpoints[0],
|
|
804
|
-
validPayload: "{}"
|
|
805
|
-
},
|
|
806
|
-
confidence: 0.9,
|
|
807
|
-
generated: true
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Retry storm for any API
|
|
812
|
-
if (analysis.routes.api.length > 0) {
|
|
813
|
-
scenarios.push({
|
|
814
|
-
...templates.retryStorm,
|
|
815
|
-
category: "api",
|
|
816
|
-
variables: {
|
|
817
|
-
apiPattern: analysis.routes.api[0],
|
|
818
|
-
pageWithApi: "/" // Will be refined based on route analysis
|
|
819
|
-
},
|
|
820
|
-
confidence: 0.8,
|
|
821
|
-
generated: true
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// Rate limiting
|
|
826
|
-
scenarios.push({
|
|
827
|
-
...templates.rateLimiting,
|
|
828
|
-
category: "api",
|
|
829
|
-
variables: {
|
|
830
|
-
rateLimitedEndpoint: analysis.routes.api[0] || "/api/data"
|
|
831
|
-
},
|
|
832
|
-
confidence: 0.85,
|
|
833
|
-
generated: true
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Generate business logic scenarios
|
|
841
|
-
*/
|
|
842
|
-
generateBusinessScenarios(analysis) {
|
|
843
|
-
const scenarios = [];
|
|
844
|
-
const templates = SCENARIO_TEMPLATES.business;
|
|
845
|
-
|
|
846
|
-
if (!analysis.features.hasCheckout) return scenarios;
|
|
847
|
-
|
|
848
|
-
// Price manipulation
|
|
849
|
-
scenarios.push({
|
|
850
|
-
...templates.priceManipulation,
|
|
851
|
-
category: "business",
|
|
852
|
-
variables: {
|
|
853
|
-
testProduct: "test-product-id"
|
|
854
|
-
},
|
|
855
|
-
confidence: 0.95,
|
|
856
|
-
generated: true
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
// Inventory race condition
|
|
860
|
-
scenarios.push({
|
|
861
|
-
...templates.inventoryRace,
|
|
862
|
-
category: "business",
|
|
863
|
-
variables: {
|
|
864
|
-
testProduct: "test-product-id"
|
|
865
|
-
},
|
|
866
|
-
confidence: 0.9,
|
|
867
|
-
generated: true
|
|
868
|
-
});
|
|
869
|
-
|
|
870
|
-
// Negative quantity
|
|
871
|
-
scenarios.push({
|
|
872
|
-
...templates.negativeQuantity,
|
|
873
|
-
category: "business",
|
|
874
|
-
variables: {},
|
|
875
|
-
confidence: 0.95,
|
|
876
|
-
generated: true
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
// Checkout abandonment
|
|
880
|
-
scenarios.push({
|
|
881
|
-
...templates.checkoutAbandonment,
|
|
882
|
-
category: "business",
|
|
883
|
-
variables: {
|
|
884
|
-
testProduct: "test-product-id"
|
|
885
|
-
},
|
|
886
|
-
confidence: 0.8,
|
|
887
|
-
generated: true
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
* Generate error recovery scenarios
|
|
895
|
-
*/
|
|
896
|
-
generateErrorScenarios(analysis) {
|
|
897
|
-
const scenarios = [];
|
|
898
|
-
const templates = SCENARIO_TEMPLATES.errors;
|
|
899
|
-
|
|
900
|
-
const apiPattern = analysis.routes.api[0] || "/api/*";
|
|
901
|
-
const formPage = analysis.routes.forms[0] || "/";
|
|
902
|
-
|
|
903
|
-
// Network drop
|
|
904
|
-
scenarios.push({
|
|
905
|
-
...templates.networkDrop,
|
|
906
|
-
category: "errors",
|
|
907
|
-
variables: {
|
|
908
|
-
pageWithForm: formPage,
|
|
909
|
-
formSelector: "form",
|
|
910
|
-
submitButton: "[type='submit']",
|
|
911
|
-
retryButton: "button:has-text('Retry'), button:has-text('Try Again')"
|
|
912
|
-
},
|
|
913
|
-
confidence: 0.85,
|
|
914
|
-
generated: true
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// Partial response
|
|
918
|
-
scenarios.push({
|
|
919
|
-
...templates.partialResponse,
|
|
920
|
-
category: "errors",
|
|
921
|
-
variables: {
|
|
922
|
-
apiPattern,
|
|
923
|
-
pageWithApi: "/"
|
|
924
|
-
},
|
|
925
|
-
confidence: 0.8,
|
|
926
|
-
generated: true
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
// 500 error
|
|
930
|
-
scenarios.push({
|
|
931
|
-
...templates.serverError,
|
|
932
|
-
category: "errors",
|
|
933
|
-
variables: {
|
|
934
|
-
apiPattern,
|
|
935
|
-
pageWithApi: "/"
|
|
936
|
-
},
|
|
937
|
-
confidence: 0.85,
|
|
938
|
-
generated: true
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Generate state machine scenarios
|
|
946
|
-
*/
|
|
947
|
-
generateStateMachineScenarios(analysis) {
|
|
948
|
-
const scenarios = [];
|
|
949
|
-
const templates = SCENARIO_TEMPLATES.stateMachine;
|
|
950
|
-
|
|
951
|
-
if (!analysis.features.hasWizard) return scenarios;
|
|
952
|
-
|
|
953
|
-
const wizardRoutes = analysis.routes.wizard;
|
|
954
|
-
if (wizardRoutes.length >= 2) {
|
|
955
|
-
// Skip step
|
|
956
|
-
scenarios.push({
|
|
957
|
-
...templates.skipStep,
|
|
958
|
-
category: "stateMachine",
|
|
959
|
-
variables: {
|
|
960
|
-
wizardStep1: wizardRoutes[0],
|
|
961
|
-
wizardStep3: wizardRoutes[2] || wizardRoutes[1]
|
|
962
|
-
},
|
|
963
|
-
confidence: 0.9,
|
|
964
|
-
generated: true
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Direct URL access
|
|
969
|
-
scenarios.push({
|
|
970
|
-
...templates.directUrlAccess,
|
|
971
|
-
category: "stateMachine",
|
|
972
|
-
variables: {
|
|
973
|
-
confirmationPage: "/checkout/confirmation",
|
|
974
|
-
startPage: "/checkout"
|
|
975
|
-
},
|
|
976
|
-
confidence: 0.85,
|
|
977
|
-
generated: true
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
// Back button abuse
|
|
981
|
-
scenarios.push({
|
|
982
|
-
...templates.backButtonAbuse,
|
|
983
|
-
category: "stateMachine",
|
|
984
|
-
variables: {
|
|
985
|
-
checkoutFlow: "checkout",
|
|
986
|
-
submitButton: "[type='submit']"
|
|
987
|
-
},
|
|
988
|
-
confidence: 0.8,
|
|
989
|
-
generated: true
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Generate performance scenarios
|
|
997
|
-
*/
|
|
998
|
-
generatePerformanceScenarios(analysis) {
|
|
999
|
-
const scenarios = [];
|
|
1000
|
-
const templates = SCENARIO_TEMPLATES.performance;
|
|
1001
|
-
|
|
1002
|
-
// Large file upload
|
|
1003
|
-
if (analysis.features.hasUpload) {
|
|
1004
|
-
scenarios.push({
|
|
1005
|
-
...templates.largeFileUpload,
|
|
1006
|
-
category: "performance",
|
|
1007
|
-
variables: {
|
|
1008
|
-
uploadPage: analysis.routes.uploads[0] || "/upload"
|
|
1009
|
-
},
|
|
1010
|
-
confidence: 0.85,
|
|
1011
|
-
generated: true
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
// File type spoofing
|
|
1015
|
-
scenarios.push({
|
|
1016
|
-
...templates.fileTypeSpoofing,
|
|
1017
|
-
category: "performance",
|
|
1018
|
-
variables: {
|
|
1019
|
-
uploadPage: analysis.routes.uploads[0] || "/upload"
|
|
1020
|
-
},
|
|
1021
|
-
confidence: 0.9,
|
|
1022
|
-
generated: true
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// Infinite scroll memory leak (if detected or assume present)
|
|
1027
|
-
scenarios.push({
|
|
1028
|
-
...templates.infiniteScroll,
|
|
1029
|
-
category: "performance",
|
|
1030
|
-
variables: {
|
|
1031
|
-
infiniteScrollPage: "/feed"
|
|
1032
|
-
},
|
|
1033
|
-
confidence: 0.7,
|
|
1034
|
-
generated: true
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
return scenarios.slice(0, this.maxScenariosPerCategory);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Prioritize and deduplicate scenarios
|
|
1042
|
-
*/
|
|
1043
|
-
prioritizeScenarios(scenarios) {
|
|
1044
|
-
// Sort by: severity (BLOCK first), then confidence
|
|
1045
|
-
const severityOrder = { BLOCK: 0, WARN: 1, INFO: 2 };
|
|
1046
|
-
|
|
1047
|
-
return scenarios.sort((a, b) => {
|
|
1048
|
-
const sevDiff = severityOrder[a.severity] - severityOrder[b.severity];
|
|
1049
|
-
if (sevDiff !== 0) return sevDiff;
|
|
1050
|
-
return b.confidence - a.confidence;
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Export scenarios for Reality Mode execution
|
|
1056
|
-
*/
|
|
1057
|
-
exportForReality(scenarios) {
|
|
1058
|
-
return scenarios.map(scenario => ({
|
|
1059
|
-
id: `${scenario.category}-${scenario.name.toLowerCase().replace(/\s+/g, "-")}`,
|
|
1060
|
-
name: scenario.name,
|
|
1061
|
-
description: scenario.description,
|
|
1062
|
-
category: scenario.category,
|
|
1063
|
-
severity: scenario.severity,
|
|
1064
|
-
confidence: scenario.confidence,
|
|
1065
|
-
steps: this.resolveVariables(scenario.steps, scenario.variables),
|
|
1066
|
-
generated: scenario.generated || false,
|
|
1067
|
-
expectedOutcomes: this.extractExpectations(scenario.steps)
|
|
1068
|
-
}));
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
/**
|
|
1072
|
-
* Resolve template variables in steps
|
|
1073
|
-
*/
|
|
1074
|
-
resolveVariables(steps, variables) {
|
|
1075
|
-
if (!steps || !variables) return steps;
|
|
1076
|
-
|
|
1077
|
-
return JSON.parse(
|
|
1078
|
-
JSON.stringify(steps).replace(
|
|
1079
|
-
/\{\{(\w+)\}\}/g,
|
|
1080
|
-
(match, varName) => variables[varName] || match
|
|
1081
|
-
)
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
/**
|
|
1086
|
-
* Extract expected outcomes from steps
|
|
1087
|
-
*/
|
|
1088
|
-
extractExpectations(steps) {
|
|
1089
|
-
if (!steps) return [];
|
|
1090
|
-
|
|
1091
|
-
return steps
|
|
1092
|
-
.filter(step => step.action === "expect")
|
|
1093
|
-
.map(step => step.params);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1098
|
-
// SCENARIO EXECUTOR (integrates with Playwright)
|
|
1099
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1100
|
-
|
|
1101
|
-
class ScenarioExecutor {
|
|
1102
|
-
constructor(options = {}) {
|
|
1103
|
-
this.page = options.page;
|
|
1104
|
-
this.context = options.context;
|
|
1105
|
-
this.baseUrl = options.baseUrl;
|
|
1106
|
-
this.timeout = options.timeout || 30000;
|
|
1107
|
-
this.results = [];
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Execute a single scenario
|
|
1112
|
-
*/
|
|
1113
|
-
async executeScenario(scenario) {
|
|
1114
|
-
const result = {
|
|
1115
|
-
id: scenario.id,
|
|
1116
|
-
name: scenario.name,
|
|
1117
|
-
category: scenario.category,
|
|
1118
|
-
severity: scenario.severity,
|
|
1119
|
-
status: "pending",
|
|
1120
|
-
steps: [],
|
|
1121
|
-
violations: [],
|
|
1122
|
-
duration: 0
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
|
-
const startTime = Date.now();
|
|
1126
|
-
|
|
1127
|
-
try {
|
|
1128
|
-
for (const step of scenario.steps) {
|
|
1129
|
-
const stepResult = await this.executeStep(step);
|
|
1130
|
-
result.steps.push(stepResult);
|
|
1131
|
-
|
|
1132
|
-
if (stepResult.status === "failed" && step.action === "expect") {
|
|
1133
|
-
result.violations.push({
|
|
1134
|
-
step: step.action,
|
|
1135
|
-
expected: step.params,
|
|
1136
|
-
actual: stepResult.actual,
|
|
1137
|
-
message: stepResult.error
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
result.status = result.violations.length > 0 ? "failed" : "passed";
|
|
1143
|
-
} catch (error) {
|
|
1144
|
-
result.status = "error";
|
|
1145
|
-
result.error = error.message;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
result.duration = Date.now() - startTime;
|
|
1149
|
-
this.results.push(result);
|
|
1150
|
-
|
|
1151
|
-
return result;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Execute a single step
|
|
1156
|
-
*/
|
|
1157
|
-
async executeStep(step) {
|
|
1158
|
-
const stepResult = {
|
|
1159
|
-
action: step.action,
|
|
1160
|
-
params: step.params,
|
|
1161
|
-
status: "pending",
|
|
1162
|
-
timestamp: Date.now()
|
|
1163
|
-
};
|
|
1164
|
-
|
|
1165
|
-
try {
|
|
1166
|
-
switch (step.action) {
|
|
1167
|
-
case "navigate":
|
|
1168
|
-
await this.page.goto(this.resolveUrl(step.params.to));
|
|
1169
|
-
stepResult.status = "passed";
|
|
1170
|
-
break;
|
|
1171
|
-
|
|
1172
|
-
case "click":
|
|
1173
|
-
await this.page.click(step.params.selector, { timeout: this.timeout });
|
|
1174
|
-
stepResult.status = "passed";
|
|
1175
|
-
break;
|
|
1176
|
-
|
|
1177
|
-
case "fill":
|
|
1178
|
-
await this.page.fill(step.params.selector, step.params.value);
|
|
1179
|
-
stepResult.status = "passed";
|
|
1180
|
-
break;
|
|
1181
|
-
|
|
1182
|
-
case "doubleClick":
|
|
1183
|
-
const element = await this.page.$(step.params.selector);
|
|
1184
|
-
if (element) {
|
|
1185
|
-
await element.click();
|
|
1186
|
-
await this.page.waitForTimeout(step.params.delay || 50);
|
|
1187
|
-
await element.click();
|
|
1188
|
-
}
|
|
1189
|
-
stepResult.status = "passed";
|
|
1190
|
-
break;
|
|
1191
|
-
|
|
1192
|
-
case "goOffline":
|
|
1193
|
-
await this.context.setOffline(true);
|
|
1194
|
-
stepResult.status = "passed";
|
|
1195
|
-
break;
|
|
1196
|
-
|
|
1197
|
-
case "goOnline":
|
|
1198
|
-
await this.context.setOffline(false);
|
|
1199
|
-
stepResult.status = "passed";
|
|
1200
|
-
break;
|
|
1201
|
-
|
|
1202
|
-
case "clearCookies":
|
|
1203
|
-
await this.context.clearCookies();
|
|
1204
|
-
stepResult.status = "passed";
|
|
1205
|
-
break;
|
|
1206
|
-
|
|
1207
|
-
case "wait":
|
|
1208
|
-
await this.page.waitForTimeout(step.params.ms);
|
|
1209
|
-
stepResult.status = "passed";
|
|
1210
|
-
break;
|
|
1211
|
-
|
|
1212
|
-
case "goBack":
|
|
1213
|
-
await this.page.goBack();
|
|
1214
|
-
stepResult.status = "passed";
|
|
1215
|
-
break;
|
|
1216
|
-
|
|
1217
|
-
case "expect":
|
|
1218
|
-
stepResult.actual = await this.evaluateExpectation(step.params);
|
|
1219
|
-
stepResult.status = stepResult.actual.passed ? "passed" : "failed";
|
|
1220
|
-
if (!stepResult.actual.passed) {
|
|
1221
|
-
stepResult.error = stepResult.actual.reason;
|
|
1222
|
-
}
|
|
1223
|
-
break;
|
|
1224
|
-
|
|
1225
|
-
case "recordMemory":
|
|
1226
|
-
stepResult.memory = await this.page.evaluate(() => {
|
|
1227
|
-
if (window.performance && window.performance.memory) {
|
|
1228
|
-
return window.performance.memory.usedJSHeapSize;
|
|
1229
|
-
}
|
|
1230
|
-
return null;
|
|
1231
|
-
});
|
|
1232
|
-
stepResult.status = "passed";
|
|
1233
|
-
break;
|
|
1234
|
-
|
|
1235
|
-
case "interceptRequest":
|
|
1236
|
-
await this.page.route(step.params.pattern, (route, request) => {
|
|
1237
|
-
const postData = request.postData();
|
|
1238
|
-
if (postData && step.params.modify) {
|
|
1239
|
-
const modified = { ...JSON.parse(postData), ...step.params.modify };
|
|
1240
|
-
route.continue({ postData: JSON.stringify(modified) });
|
|
1241
|
-
} else {
|
|
1242
|
-
route.continue();
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
stepResult.status = "passed";
|
|
1246
|
-
break;
|
|
1247
|
-
|
|
1248
|
-
case "interceptResponse":
|
|
1249
|
-
await this.page.route(step.params.pattern, route => {
|
|
1250
|
-
if (step.params.status) {
|
|
1251
|
-
route.fulfill({
|
|
1252
|
-
status: step.params.status,
|
|
1253
|
-
body: step.params.body || ""
|
|
1254
|
-
});
|
|
1255
|
-
} else if (step.params.truncate) {
|
|
1256
|
-
route.continue();
|
|
1257
|
-
} else {
|
|
1258
|
-
route.continue();
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
stepResult.status = "passed";
|
|
1262
|
-
break;
|
|
1263
|
-
|
|
1264
|
-
case "parallel":
|
|
1265
|
-
// Execute parallel actions (simplified)
|
|
1266
|
-
const promises = [];
|
|
1267
|
-
for (let i = 0; i < step.params.count; i++) {
|
|
1268
|
-
promises.push(this.executeStep({ action: step.params.action, params: step.params }));
|
|
1269
|
-
}
|
|
1270
|
-
await Promise.all(promises);
|
|
1271
|
-
stepResult.status = "passed";
|
|
1272
|
-
break;
|
|
1273
|
-
|
|
1274
|
-
default:
|
|
1275
|
-
stepResult.status = "skipped";
|
|
1276
|
-
stepResult.reason = `Unknown action: ${step.action}`;
|
|
1277
|
-
}
|
|
1278
|
-
} catch (error) {
|
|
1279
|
-
stepResult.status = "error";
|
|
1280
|
-
stepResult.error = error.message;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
return stepResult;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
/**
|
|
1287
|
-
* Evaluate an expectation
|
|
1288
|
-
*/
|
|
1289
|
-
async evaluateExpectation(params) {
|
|
1290
|
-
const result = { passed: true, checks: [] };
|
|
1291
|
-
|
|
1292
|
-
// Check for redirect
|
|
1293
|
-
if (params.redirect) {
|
|
1294
|
-
const currentUrl = this.page.url();
|
|
1295
|
-
const expectedUrl = this.resolveUrl(params.redirect);
|
|
1296
|
-
const redirectPassed = currentUrl.includes(params.redirect);
|
|
1297
|
-
result.checks.push({ type: "redirect", expected: expectedUrl, actual: currentUrl, passed: redirectPassed });
|
|
1298
|
-
if (!redirectPassed && !params.or) result.passed = false;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
// Check for status code
|
|
1302
|
-
if (params.status) {
|
|
1303
|
-
// Would need to intercept the response to check this
|
|
1304
|
-
result.checks.push({ type: "status", expected: params.status, passed: true });
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
// Check for validation messages
|
|
1308
|
-
if (params.validation) {
|
|
1309
|
-
const hasValidation = await this.page.$("[class*='error'], [class*='invalid'], [aria-invalid='true']");
|
|
1310
|
-
result.checks.push({ type: "validation", expected: true, actual: !!hasValidation, passed: !!hasValidation });
|
|
1311
|
-
if (!hasValidation && !params.or) result.passed = false;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// Check for user-friendly error (no stack trace)
|
|
1315
|
-
if (params.noStackTrace) {
|
|
1316
|
-
const pageContent = await this.page.content();
|
|
1317
|
-
const hasStackTrace = /at\s+\w+\s+\(|Error:\s+/i.test(pageContent);
|
|
1318
|
-
result.checks.push({ type: "noStackTrace", passed: !hasStackTrace });
|
|
1319
|
-
if (hasStackTrace) {
|
|
1320
|
-
result.passed = false;
|
|
1321
|
-
result.reason = "Stack trace exposed to user";
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// Check for JS errors (page didn't crash)
|
|
1326
|
-
if (params.noJsCrash) {
|
|
1327
|
-
const hasContent = await this.page.$("body");
|
|
1328
|
-
result.checks.push({ type: "noJsCrash", passed: !!hasContent });
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
// Memory growth check
|
|
1332
|
-
if (params.memoryGrowth) {
|
|
1333
|
-
// Would compare recorded memory values
|
|
1334
|
-
result.checks.push({ type: "memoryGrowth", passed: true });
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Generate overall result
|
|
1338
|
-
if (!result.passed) {
|
|
1339
|
-
const failedChecks = result.checks.filter(c => !c.passed);
|
|
1340
|
-
result.reason = failedChecks.map(c => `${c.type}: expected ${c.expected}, got ${c.actual}`).join("; ");
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
return result;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/**
|
|
1347
|
-
* Resolve URL with base
|
|
1348
|
-
*/
|
|
1349
|
-
resolveUrl(path) {
|
|
1350
|
-
if (path.startsWith("http")) return path;
|
|
1351
|
-
return new URL(path, this.baseUrl).toString();
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
/**
|
|
1355
|
-
* Get execution summary
|
|
1356
|
-
*/
|
|
1357
|
-
getSummary() {
|
|
1358
|
-
const passed = this.results.filter(r => r.status === "passed").length;
|
|
1359
|
-
const failed = this.results.filter(r => r.status === "failed").length;
|
|
1360
|
-
const errors = this.results.filter(r => r.status === "error").length;
|
|
1361
|
-
|
|
1362
|
-
return {
|
|
1363
|
-
total: this.results.length,
|
|
1364
|
-
passed,
|
|
1365
|
-
failed,
|
|
1366
|
-
errors,
|
|
1367
|
-
passRate: this.results.length > 0 ? (passed / this.results.length * 100).toFixed(1) : 0,
|
|
1368
|
-
violations: this.results.flatMap(r => r.violations),
|
|
1369
|
-
byCategory: this.groupByCategory(),
|
|
1370
|
-
bySeverity: this.groupBySeverity()
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
groupByCategory() {
|
|
1375
|
-
const groups = {};
|
|
1376
|
-
for (const result of this.results) {
|
|
1377
|
-
if (!groups[result.category]) {
|
|
1378
|
-
groups[result.category] = { passed: 0, failed: 0, errors: 0 };
|
|
1379
|
-
}
|
|
1380
|
-
groups[result.category][result.status]++;
|
|
1381
|
-
}
|
|
1382
|
-
return groups;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
groupBySeverity() {
|
|
1386
|
-
const groups = { BLOCK: [], WARN: [] };
|
|
1387
|
-
for (const result of this.results) {
|
|
1388
|
-
if (result.status === "failed") {
|
|
1389
|
-
groups[result.severity]?.push(result);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
return groups;
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1397
|
-
// EXPORTS
|
|
1398
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1399
|
-
|
|
1400
|
-
module.exports = {
|
|
1401
|
-
ScenarioGenerator,
|
|
1402
|
-
ScenarioExecutor,
|
|
1403
|
-
SCENARIO_TEMPLATES
|
|
1404
|
-
};
|