swipium 1.4.0 → 2.0.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 +313 -0
- package/README.md +175 -219
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +39 -26
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +68 -20
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +31 -5
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +23 -5
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +44 -8
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +24 -6
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/fsWalk.js +19 -2
- package/dist/appMap/fsWalk.js.map +1 -1
- package/dist/appMap/issues.js +10 -30
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +6 -6
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +83 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +21 -5
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +12 -5
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +6 -2
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +151 -39
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +58 -85
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +34 -12
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +49 -12
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +41 -14
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +112 -30
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +6 -2
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +71 -23
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +15 -5
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +22 -28
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +14 -8
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +9 -3
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +16 -8
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +6 -6
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +288 -121
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +76 -27
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +278 -107
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/automationGen/run.js +388 -0
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +17 -10
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +52 -12
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +235 -31
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +39 -18
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +36 -15
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +7 -7
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +55 -18
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +22 -8
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +10 -8
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +160 -39
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +317 -43
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +124 -8
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +365 -55
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +7 -3
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +15 -6
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/memory.js +4 -1
- package/dist/explore/memory.js.map +1 -1
- package/dist/explore/planner.js +54 -9
- package/dist/explore/planner.js.map +1 -1
- package/dist/explore/policy.js +11 -6
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +363 -190
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +4 -2
- package/dist/explore/signatures.js.map +1 -1
- package/dist/explore/suite.js +3 -1
- package/dist/explore/suite.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +137 -29
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +6 -3
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +101 -20
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +44 -15
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +15 -11
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +16 -8
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +8 -5
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +8 -2
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +32 -4
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +27 -15
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +34 -12
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +9 -6
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +16 -13
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +92 -31
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +25 -15
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +45 -11
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +72 -9
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +68 -17
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/lint.js +88 -14
- package/dist/flows/lint.js.map +1 -1
- package/dist/flows/pack.js +21 -5
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +176 -37
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +308 -99
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +107 -25
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +8 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +28 -20
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +35 -9
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +93 -46
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +6 -5
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +12 -8
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +40 -9
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +84 -8
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +53 -12
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +12 -3
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +399 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +9 -6
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +4 -3
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +94 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +7 -5
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +6 -3
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +78 -17
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +321 -25
- package/dist/lib/wda.js.map +1 -1
- package/dist/lib/wdaConfig.js +12 -5
- package/dist/lib/wdaConfig.js.map +1 -1
- package/dist/lib/wdaTune.js +52 -23
- package/dist/lib/wdaTune.js.map +1 -1
- package/dist/mobileAudit/checks.js +150 -28
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +121 -20
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +9 -4
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +214 -35
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +1085 -145
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +148 -26
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +148 -19
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +7 -13
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +167 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +413 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +519 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +136 -0
- package/dist/orchestration/testThis/terminal.js.map +1 -0
- package/dist/orchestration/testThis/types.js +12 -0
- package/dist/orchestration/testThis/types.js.map +1 -0
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +24 -24
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/coverage.js +0 -1
- package/dist/report/coverage.js.map +1 -1
- package/dist/report/evidence.js.map +1 -1
- package/dist/report/export.js +449 -72
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/flake.js +13 -14
- package/dist/report/flake.js.map +1 -1
- package/dist/report/history.js +72 -38
- package/dist/report/history.js.map +1 -1
- package/dist/report/policy.js +18 -7
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +10 -16
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/readiness.js +42 -12
- package/dist/report/readiness.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +501 -69
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +10 -4
- package/dist/services/build.js.map +1 -1
- package/dist/services/flowGenerate.js +75 -0
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +40 -8
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +92 -18
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +94 -17
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +271 -80
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +68 -22
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +43 -6
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +78 -33
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +284 -4
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +433 -0
- package/dist/session/processRegistry.js.map +1 -0
- package/dist/session/progress.js +1 -1
- package/dist/session/progress.js.map +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +459 -59
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +60 -10
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +30 -12
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +36 -6
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +82 -11
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +15 -6
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +22 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +75 -44
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +15 -11
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +7 -3
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +43 -14
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +13 -7
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +21 -16
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +12 -4
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +868 -174
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +521 -112
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +82 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +393 -189
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +92 -51
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +141 -40
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +98 -18
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +108 -19
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +98 -35
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +110 -42
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +152 -130
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +94 -66
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +347 -132
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +42 -10
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +199 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +37 -8
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +17 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +148 -194
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +256 -171
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +55 -11
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +84 -38
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +83 -26
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +36 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +31 -17
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -14
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +193 -47
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +16 -8
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +12 -12
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +81 -18
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +85 -20
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +42 -18
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +29 -12
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +157 -107
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +117 -84
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +391 -310
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +112 -36
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +35 -802
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +698 -116
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -23
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +312 -54
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +37 -64
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +65 -8
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +87 -15
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +210 -104
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +808 -217
- package/package.json +16 -6
- package/dist/appMap/diff.js +0 -60
- package/dist/appMap/diff.js.map +0 -1
- package/dist/automation/maestroIr.js +0 -282
- package/dist/automation/maestroIr.js.map +0 -1
- package/dist/interop/maestro.js +0 -151
- package/dist/interop/maestro.js.map +0 -1
- package/dist/tools/assertVisual.js +0 -74
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js +0 -394
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -209
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js +0 -55
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -56
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/idling.js +0 -104
- package/dist/tools/idling.js.map +0 -1
- package/dist/tools/inputCapabilities.js +0 -30
- package/dist/tools/inputCapabilities.js.map +0 -1
- package/dist/tools/locator.js +0 -57
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
- package/dist/tools/permissions.js +0 -149
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -49
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -70
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -101
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -170
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -61
- package/dist/tools/visualText.js.map +0 -1
package/dist/tools/suite.js
CHANGED
|
@@ -1,36 +1,43 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Per-run suite generation cores + qa_flow_compile.
|
|
2
2
|
//
|
|
3
3
|
// These turn the actions recorded during a session (qa_act) into a MAINTAINABLE POM suite under
|
|
4
|
-
// .swipium
|
|
5
|
-
// catalog, and a locator audit
|
|
4
|
+
// .swipium/: page objects (selectors), tests (reference elements by name), a suite, a test-case
|
|
5
|
+
// catalog, and a locator audit. The generation entry points (page objects, full suite, test-case
|
|
6
|
+
// catalog) are exposed through qa_generate (src/tools/generate.ts); this file registers only
|
|
7
|
+
// qa_flow_compile, which compiles a generated POM suite into runnable Flow V2 for qa_flow_run.
|
|
6
8
|
import { z } from 'zod';
|
|
7
9
|
import { createHash } from 'node:crypto';
|
|
8
10
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
11
|
import { join, dirname } from 'node:path';
|
|
10
|
-
import { qaOk, qaError } from '../lib/result.js';
|
|
12
|
+
import { qaOk, qaError, unknownSessionError } from '../lib/result.js';
|
|
13
|
+
import { resolveProjectRoot, unresolvedProjectRootError } from '../context/projectRoot.js';
|
|
11
14
|
import { requireConsent, consumeConsent } from '../consent/consent.js';
|
|
12
15
|
import { generatePom } from '../suite/pom.js';
|
|
13
16
|
import { generateTestCases } from '../suite/testcase.js';
|
|
14
|
-
import { lintSuitePages } from '../suite/lint.js';
|
|
15
17
|
import { compileSuite } from '../suite/compile.js';
|
|
16
18
|
import { parseFlow } from '../flows/schema.js';
|
|
17
19
|
import { runFlow } from '../flows/run.js';
|
|
18
20
|
import { loadProjectConfig } from '../cli/scan.js';
|
|
19
21
|
import { generateAndCompileSuite } from '../services/suiteGenerate.js';
|
|
22
|
+
import { assertNoSecretLeaks, findSecretLeaks, inputBindings, secretSafeNotes, structuralLiterals } from '../suite/secretGuard.js';
|
|
20
23
|
import { getDriver } from '../session/attach.js';
|
|
21
24
|
import { readinessForSession } from '../report/readiness.js';
|
|
22
25
|
import { loadStateProfile, prepareStateProfile, teardownStateProfile, verifyStateProfile } from '../state/profile.js';
|
|
23
|
-
import { hasStateProfileMutation, stateProfileAffects, stateRisk } from '
|
|
26
|
+
import { hasStateProfileMutation, stateProfileAffects, stateRisk } from '../state/consent.js';
|
|
24
27
|
function stable(value) {
|
|
25
28
|
if (Array.isArray(value))
|
|
26
29
|
return value.map(stable);
|
|
27
30
|
if (value && typeof value === 'object') {
|
|
28
|
-
return Object.fromEntries(Object.entries(value)
|
|
31
|
+
return Object.fromEntries(Object.entries(value)
|
|
32
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
33
|
+
.map(([k, v]) => [k, stable(v)]));
|
|
29
34
|
}
|
|
30
35
|
return value;
|
|
31
36
|
}
|
|
32
37
|
function hash(value) {
|
|
33
|
-
return createHash('sha256')
|
|
38
|
+
return createHash('sha256')
|
|
39
|
+
.update(JSON.stringify(stable(value)))
|
|
40
|
+
.digest('hex');
|
|
34
41
|
}
|
|
35
42
|
function hashFile(path) {
|
|
36
43
|
if (!path)
|
|
@@ -54,9 +61,9 @@ function compiledFlowIdentity(c) {
|
|
|
54
61
|
};
|
|
55
62
|
}
|
|
56
63
|
function appIdOf(session) {
|
|
57
|
-
return session.appId ??
|
|
64
|
+
return session.appId ?? loadProjectConfig(session.root)?.appId ?? undefined;
|
|
58
65
|
}
|
|
59
|
-
/** Map the replay outcome to the test-case catalog's replay status (Deliverable 4
|
|
66
|
+
/** Map the replay outcome to the test-case catalog's replay status (Deliverable 4, honest plumbing). */
|
|
60
67
|
function catalogReplayStatus(mode, results, passed) {
|
|
61
68
|
if (mode === 'none' || results.length === 0)
|
|
62
69
|
return 'not_replayed';
|
|
@@ -79,6 +86,7 @@ function suiteDir(session) {
|
|
|
79
86
|
}
|
|
80
87
|
/** Write generated files under .swipium/, returning absolute paths written. */
|
|
81
88
|
function writeFiles(session, files) {
|
|
89
|
+
assertNoSecretLeaks(files, session.secrets, 'suite generation', { structural: structuralLiterals(session.recordedActions) }); // backstop: never write a secret
|
|
82
90
|
const base = suiteDir(session);
|
|
83
91
|
const written = [];
|
|
84
92
|
for (const f of files) {
|
|
@@ -89,12 +97,29 @@ function writeFiles(session, files) {
|
|
|
89
97
|
}
|
|
90
98
|
return written;
|
|
91
99
|
}
|
|
100
|
+
/** Loud refusal when generated files would carry a registered secret value (nothing is written). */
|
|
101
|
+
function secretLeakError(session, files) {
|
|
102
|
+
const leaks = findSecretLeaks(files, session.secrets, { structural: structuralLiterals(session.recordedActions) });
|
|
103
|
+
if (!leaks.length)
|
|
104
|
+
return null;
|
|
105
|
+
return qaError({
|
|
106
|
+
what: `Not generated: a registered secret value would be written in plaintext (${leaks
|
|
107
|
+
.slice(0, 5)
|
|
108
|
+
.map((l) => `${l.path}:${l.line}`)
|
|
109
|
+
.join(', ')})`,
|
|
110
|
+
changedState: false,
|
|
111
|
+
retrySafe: false,
|
|
112
|
+
failureCode: 'SECRET_IN_GENERATED_OUTPUT',
|
|
113
|
+
nextSteps: ['Nothing was written. Re-record the credential step so it is captured as a ${VAR}, then regenerate.'],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
92
116
|
function requireActions(session) {
|
|
93
117
|
if (!session.recordedActions.length) {
|
|
94
118
|
return qaError({
|
|
95
119
|
what: 'No actions recorded in this session yet',
|
|
96
120
|
changedState: false,
|
|
97
121
|
retrySafe: true,
|
|
122
|
+
failureCode: 'NO_RECORDED_ACTIONS',
|
|
98
123
|
nextSteps: ['Drive the app with qa_act first (each action is recorded), then regenerate the suite.'],
|
|
99
124
|
});
|
|
100
125
|
}
|
|
@@ -103,312 +128,389 @@ function requireActions(session) {
|
|
|
103
128
|
function pomFor(session, name) {
|
|
104
129
|
const appId = appIdOf(session);
|
|
105
130
|
const flowName = (name ?? `${(appId ?? 'app').split('.').pop()}-smoke`).replace(/[^\w.-]+/g, '-');
|
|
106
|
-
const pom = generatePom(session.recordedActions, {
|
|
131
|
+
const pom = generatePom(session.recordedActions, {
|
|
132
|
+
name: flowName,
|
|
133
|
+
appId,
|
|
134
|
+
budgetProfile: session.budgetProfile,
|
|
135
|
+
secrets: session.secrets,
|
|
136
|
+
inputs: inputBindings(session), // typed text equal to a stored input maps to its ${SWIPIUM_TEST_*} placeholder
|
|
137
|
+
});
|
|
107
138
|
return { pom, flowName };
|
|
108
139
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
replayResults.push({ name: c.name, mode: replayMode, status: r.passed ? 'passed' : 'failed', reason: r.reason });
|
|
140
|
+
/** Core handler for qa_generate target:"pom": page objects + locator audit from recorded actions. */
|
|
141
|
+
export async function runPomGenerate(sessions, { sessionId, name, save }) {
|
|
142
|
+
const session = sessions.get(sessionId);
|
|
143
|
+
if (!session)
|
|
144
|
+
return unknownSessionError(sessionId);
|
|
145
|
+
const noActions = requireActions(session);
|
|
146
|
+
if (noActions)
|
|
147
|
+
return noActions;
|
|
148
|
+
const { pom } = pomFor(session, name);
|
|
149
|
+
const pageFiles = pom.files.filter((f) => f.path.startsWith('pages/') || f.path.startsWith('locators/'));
|
|
150
|
+
const pomLeak = secretLeakError(session, pageFiles);
|
|
151
|
+
if (pomLeak)
|
|
152
|
+
return pomLeak;
|
|
153
|
+
const written = save ? writeFiles(session, pageFiles) : [];
|
|
154
|
+
const summary = `Generated ${pom.pages.length} page object(s): ${pom.pages.map((p) => p.name).join(', ')}\n` +
|
|
155
|
+
`locator audit: ${pom.audit.durable} durable / ${pom.audit.semi} semi / ${pom.audit.brittle} brittle (${pom.audit.brittlePct}% brittle)` +
|
|
156
|
+
(save ? `\nsaved ${written.length} files under .swipium/` : `\n(not saved; pass save:true)`);
|
|
157
|
+
return qaOk({ pages: pom.pages, audit: pom.audit, files: pageFiles, written }, summary);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Core handler for qa_generate target:"suite": the full per-run .swipium/ suite from recorded
|
|
161
|
+
* actions: pages + tests + suite + test cases + locator audit, plus compile and replay gates.
|
|
162
|
+
*/
|
|
163
|
+
export async function runSuiteGenerate(sessions, { sessionId, name, save, compile, replay, stateProfile, consentId, approve }) {
|
|
164
|
+
const session = sessions.get(sessionId);
|
|
165
|
+
if (!session)
|
|
166
|
+
return unknownSessionError(sessionId);
|
|
167
|
+
const noActions = requireActions(session);
|
|
168
|
+
if (noActions)
|
|
169
|
+
return noActions;
|
|
170
|
+
const res = generateAndCompileSuite(sessions, session, { name, save: save !== false, compile: compile !== false });
|
|
171
|
+
if (res.failureCode)
|
|
172
|
+
return qaError({
|
|
173
|
+
what: `Suite not generated: ${res.skippedReason}`,
|
|
174
|
+
changedState: false,
|
|
175
|
+
retrySafe: false,
|
|
176
|
+
failureCode: 'SECRET_IN_GENERATED_OUTPUT',
|
|
177
|
+
nextSteps: [res.recommendation ?? 'Re-record the credential step so it is captured as a ${VAR}, then regenerate.'],
|
|
178
|
+
});
|
|
179
|
+
const audit = res.audit;
|
|
180
|
+
const ranOk = res.compiledFlows.filter((c) => c.ok).length;
|
|
181
|
+
const replayMode = replay ?? 'dry_run';
|
|
182
|
+
const replayResults = [];
|
|
183
|
+
const replayEvidence = {
|
|
184
|
+
stateLedgerUris: [],
|
|
185
|
+
consentRequired: false,
|
|
186
|
+
consentApproved: false,
|
|
187
|
+
};
|
|
188
|
+
if (replayMode === 'dry_run') {
|
|
189
|
+
for (const c of res.compiledFlows)
|
|
190
|
+
replayResults.push({
|
|
191
|
+
name: c.name,
|
|
192
|
+
mode: replayMode,
|
|
193
|
+
status: c.ok ? 'validated' : 'failed',
|
|
194
|
+
reason: c.errors.join('; ') || undefined,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else if (replayMode === 'same_session') {
|
|
198
|
+
const { driver } = await getDriver(session);
|
|
199
|
+
if (!driver)
|
|
200
|
+
replayResults.push({
|
|
201
|
+
name: res.name ?? 'suite',
|
|
202
|
+
mode: replayMode,
|
|
203
|
+
status: 'blocked',
|
|
204
|
+
reason: 'no driver attached for same-session replay',
|
|
205
|
+
});
|
|
206
|
+
else {
|
|
207
|
+
for (const c of res.compiledFlows.filter((f) => f.ok && f.flowPath)) {
|
|
208
|
+
const parsed = parseFlow(readFileSync(c.flowPath, 'utf8'));
|
|
209
|
+
if (!parsed.flow) {
|
|
210
|
+
replayResults.push({ name: c.name, mode: replayMode, status: 'failed', reason: parsed.errors.join('; ') });
|
|
211
|
+
continue;
|
|
182
212
|
}
|
|
213
|
+
const r = await runFlow(sessions, session, driver, parsed.flow, { variables: sessions.inputVariables(session) });
|
|
214
|
+
replayResults.push({ name: c.name, mode: replayMode, status: r.passed ? 'passed' : 'failed', reason: r.reason });
|
|
183
215
|
}
|
|
184
216
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
}
|
|
218
|
+
else if (replayMode === 'fresh_state') {
|
|
219
|
+
const { driver } = await getDriver(session);
|
|
220
|
+
if (!stateProfile)
|
|
221
|
+
replayResults.push({
|
|
222
|
+
name: res.name ?? 'suite',
|
|
223
|
+
mode: replayMode,
|
|
224
|
+
status: 'blocked',
|
|
225
|
+
reason: 'stateProfile is required for fresh_state replay',
|
|
226
|
+
});
|
|
227
|
+
else if (!driver)
|
|
228
|
+
replayResults.push({
|
|
229
|
+
name: res.name ?? 'suite',
|
|
230
|
+
mode: replayMode,
|
|
231
|
+
status: 'blocked',
|
|
232
|
+
reason: 'no driver attached for fresh-state replay',
|
|
233
|
+
});
|
|
234
|
+
else {
|
|
235
|
+
const loaded = loadStateProfile(session.root, stateProfile);
|
|
236
|
+
if (!loaded.profile)
|
|
237
|
+
replayResults.push({
|
|
238
|
+
name: res.name ?? 'suite',
|
|
239
|
+
mode: replayMode,
|
|
240
|
+
status: 'blocked',
|
|
241
|
+
reason: loaded.error ?? 'could not load state profile',
|
|
242
|
+
});
|
|
191
243
|
else {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
? 'medium'
|
|
218
|
-
: 'low';
|
|
219
|
-
const gate = consumeConsent(consentId, approve, { action: 'suite_fresh_state_replay', affects });
|
|
220
|
-
if (!gate.approved) {
|
|
221
|
-
sessions.recordMutation(session, {
|
|
222
|
-
tool: 'qa_suite_generate',
|
|
223
|
-
action: 'suite_fresh_state_replay',
|
|
224
|
-
risk,
|
|
225
|
-
target: affects,
|
|
226
|
-
consent: { required: true, approved: false, payloadHash: mutationPayloadHash },
|
|
227
|
-
status: 'requested',
|
|
228
|
-
});
|
|
229
|
-
return requireConsent({
|
|
230
|
-
action: 'suite_fresh_state_replay',
|
|
231
|
-
risk,
|
|
232
|
-
exactCommand: `qa_suite_generate replay=fresh_state stateProfile=${loaded.profile.name}`,
|
|
233
|
-
affects,
|
|
234
|
-
explain: `Replay generated suite "${res.name ?? 'suite'}" from state profile "${loaded.profile.name}"? This can mutate app/device state through prepare/teardown before proving CI readiness.`,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
replayEvidence.consentApproved = true;
|
|
244
|
+
replayEvidence.stateProfile = loaded.profile.name;
|
|
245
|
+
replayEvidence.stateProfilePath = loaded.path ?? null;
|
|
246
|
+
const mutatingProfile = hasStateProfileMutation('prepare', loaded.profile, session) || hasStateProfileMutation('teardown', loaded.profile, session);
|
|
247
|
+
replayEvidence.consentRequired = mutatingProfile;
|
|
248
|
+
let mutationPayloadHash;
|
|
249
|
+
if (mutatingProfile) {
|
|
250
|
+
const prepareAffects = stateProfileAffects(session, 'prepare', loaded.profile, loaded.path);
|
|
251
|
+
const teardownAffects = stateProfileAffects(session, 'teardown', loaded.profile, loaded.path);
|
|
252
|
+
const affects = {
|
|
253
|
+
suite: res.name ?? 'suite',
|
|
254
|
+
replayMode,
|
|
255
|
+
compiledFlows: res.compiledFlows.map(compiledFlowIdentity),
|
|
256
|
+
suiteManifestPath: res.manifestPath ?? null,
|
|
257
|
+
suiteManifestHash: hashFile(res.manifestPath),
|
|
258
|
+
statePrepare: prepareAffects,
|
|
259
|
+
stateTeardown: teardownAffects,
|
|
260
|
+
};
|
|
261
|
+
mutationPayloadHash = hash(affects);
|
|
262
|
+
const risk = stateRisk('prepare', loaded.profile, session) === 'high' || stateRisk('teardown', loaded.profile, session) === 'high'
|
|
263
|
+
? 'high'
|
|
264
|
+
: stateRisk('prepare', loaded.profile, session) === 'medium' || stateRisk('teardown', loaded.profile, session) === 'medium'
|
|
265
|
+
? 'medium'
|
|
266
|
+
: 'low';
|
|
267
|
+
const gate = consumeConsent(consentId, approve, { action: 'suite_fresh_state_replay', affects });
|
|
268
|
+
if (!gate.approved) {
|
|
238
269
|
sessions.recordMutation(session, {
|
|
239
|
-
tool: '
|
|
270
|
+
tool: 'qa_generate',
|
|
240
271
|
action: 'suite_fresh_state_replay',
|
|
241
272
|
risk,
|
|
242
273
|
target: affects,
|
|
243
|
-
consent: { required: true,
|
|
244
|
-
status: '
|
|
274
|
+
consent: { required: true, approved: false, payloadHash: mutationPayloadHash },
|
|
275
|
+
status: 'requested',
|
|
276
|
+
});
|
|
277
|
+
return requireConsent({
|
|
278
|
+
action: 'suite_fresh_state_replay',
|
|
279
|
+
risk,
|
|
280
|
+
exactCommand: `qa_generate target=suite replay=fresh_state stateProfile=${loaded.profile.name}`,
|
|
281
|
+
affects,
|
|
282
|
+
explain: `Replay generated suite "${res.name ?? 'suite'}" from state profile "${loaded.profile.name}"? This can mutate app/device state through prepare/teardown before proving CI readiness.`,
|
|
245
283
|
});
|
|
246
284
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
285
|
+
replayEvidence.consentApproved = true;
|
|
286
|
+
sessions.recordMutation(session, {
|
|
287
|
+
tool: 'qa_generate',
|
|
288
|
+
action: 'suite_fresh_state_replay',
|
|
289
|
+
risk,
|
|
290
|
+
target: affects,
|
|
291
|
+
consent: { required: true, consentId, approved: true, payloadHash: mutationPayloadHash },
|
|
292
|
+
status: 'approved',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const ledgers = [];
|
|
296
|
+
const saveLedger = (action, ledger) => {
|
|
297
|
+
const uri = sessions.saveArtifact(session, 'state', `suite-${action}-${loaded.profile.name}-${Date.now()}.json`, JSON.stringify(ledger, null, 2), 'application/json', `suite fresh-state ${action} ledger`);
|
|
298
|
+
ledgers.push(uri);
|
|
299
|
+
replayEvidence.stateLedgerUris.push(uri);
|
|
300
|
+
};
|
|
301
|
+
const prepared = await prepareStateProfile(sessions, session, driver, loaded.profile);
|
|
302
|
+
saveLedger('prepare', prepared);
|
|
303
|
+
if (prepared.status === 'state_refused' || prepared.status === 'state_blocked') {
|
|
304
|
+
replayResults.push({
|
|
305
|
+
name: res.name ?? 'suite',
|
|
306
|
+
mode: replayMode,
|
|
307
|
+
status: 'blocked',
|
|
308
|
+
reason: `state prepare ${prepared.status}; ledgers=${ledgers.join(',')}`,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const verified = await verifyStateProfile(driver, loaded.profile);
|
|
313
|
+
saveLedger('verify', verified);
|
|
314
|
+
if (verified.status === 'state_blocked') {
|
|
315
|
+
replayResults.push({
|
|
316
|
+
name: res.name ?? 'suite',
|
|
317
|
+
mode: replayMode,
|
|
318
|
+
status: 'blocked',
|
|
319
|
+
reason: `state verify blocked; ledgers=${ledgers.join(',')}`,
|
|
320
|
+
});
|
|
257
321
|
}
|
|
258
322
|
else {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
else {
|
|
265
|
-
for (const c of res.compiledFlows.filter((f) => f.ok && f.flowPath)) {
|
|
266
|
-
const parsed = parseFlow(readFileSync(c.flowPath, 'utf8'));
|
|
267
|
-
if (!parsed.flow) {
|
|
268
|
-
replayResults.push({ name: c.name, mode: replayMode, status: 'failed', reason: parsed.errors.join('; ') });
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
const r = await runFlow(sessions, session, driver, parsed.flow, { variables: sessions.inputVariables(session) });
|
|
272
|
-
replayResults.push({ name: c.name, mode: replayMode, status: r.passed ? 'passed' : 'failed', reason: r.reason });
|
|
323
|
+
for (const c of res.compiledFlows.filter((f) => f.ok && f.flowPath)) {
|
|
324
|
+
const parsed = parseFlow(readFileSync(c.flowPath, 'utf8'));
|
|
325
|
+
if (!parsed.flow) {
|
|
326
|
+
replayResults.push({ name: c.name, mode: replayMode, status: 'failed', reason: parsed.errors.join('; ') });
|
|
327
|
+
continue;
|
|
273
328
|
}
|
|
329
|
+
const r = await runFlow(sessions, session, driver, parsed.flow, { variables: sessions.inputVariables(session) });
|
|
330
|
+
replayResults.push({ name: c.name, mode: replayMode, status: r.passed ? 'passed' : 'failed', reason: r.reason });
|
|
274
331
|
}
|
|
275
332
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
333
|
+
}
|
|
334
|
+
const tornDown = await teardownStateProfile(sessions, session, driver, loaded.profile);
|
|
335
|
+
saveLedger('teardown', tornDown);
|
|
336
|
+
if (mutatingProfile) {
|
|
337
|
+
const ok = replayResults.length > 0 && replayResults.every((r) => r.status === 'passed');
|
|
338
|
+
sessions.recordMutation(session, {
|
|
339
|
+
tool: 'qa_generate',
|
|
340
|
+
action: 'suite_fresh_state_replay',
|
|
341
|
+
risk: stateRisk('prepare', loaded.profile, session),
|
|
342
|
+
target: { stateProfile: loaded.profile.name, suite: res.name ?? 'suite', ledgerUris: ledgers },
|
|
343
|
+
consent: { required: true, consentId, approved: true, payloadHash: mutationPayloadHash },
|
|
344
|
+
status: ok ? 'executed' : 'blocked',
|
|
345
|
+
ledgerUri: ledgers[0],
|
|
346
|
+
detail: ok ? 'fresh_state replay passed' : 'fresh_state replay did not pass',
|
|
347
|
+
});
|
|
291
348
|
}
|
|
292
349
|
}
|
|
293
350
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
351
|
+
}
|
|
352
|
+
const replayPassed = replayResults.length > 0 && replayResults.every((r) => r.status === 'passed' || r.status === 'validated');
|
|
353
|
+
const ciReadyGate = {
|
|
354
|
+
compiled: res.suiteRunnable,
|
|
355
|
+
freshStateReplay: replayMode === 'fresh_state' && replayResults.length > 0 && replayResults.every((r) => r.status === 'passed'),
|
|
356
|
+
declaredStateProfile: replayMode === 'fresh_state' && !!stateProfile,
|
|
357
|
+
stateEvidence: replayMode === 'fresh_state' && replayEvidence.stateLedgerUris.length >= 2,
|
|
358
|
+
stableEvidence: replayMode === 'fresh_state' &&
|
|
359
|
+
replayResults.length > 0 &&
|
|
360
|
+
replayResults.every((r) => r.status === 'passed') &&
|
|
361
|
+
replayEvidence.stateLedgerUris.length >= 2,
|
|
362
|
+
};
|
|
363
|
+
const ciReady = Object.values(ciReadyGate).every(Boolean);
|
|
364
|
+
const suiteReplayed = (replayMode === 'same_session' || replayMode === 'fresh_state') && replayPassed;
|
|
365
|
+
const readinessLabels = readinessForSession(session, { suiteRunnable: res.suiteRunnable, suiteReplayed, ciReady });
|
|
366
|
+
if (!readinessLabels.includes('generated'))
|
|
367
|
+
readinessLabels.push('generated');
|
|
368
|
+
if (res.suiteRunnable && !readinessLabels.includes('compiled'))
|
|
369
|
+
readinessLabels.push('compiled');
|
|
370
|
+
if (suiteReplayed && !readinessLabels.includes('replayed'))
|
|
371
|
+
readinessLabels.push('replayed');
|
|
372
|
+
if (ciReady && !readinessLabels.includes('ci_ready'))
|
|
373
|
+
readinessLabels.push('ci_ready');
|
|
374
|
+
const readiness = readinessLabels.at(-1) ?? 'generated';
|
|
375
|
+
const written = res.manifestPath ? [...res.written] : res.written;
|
|
376
|
+
if (res.manifestPath) {
|
|
377
|
+
writeFileSync(res.manifestPath, JSON.stringify({
|
|
378
|
+
schema: 'swipium.suite.manifest.v1',
|
|
379
|
+
generatedAt: new Date(session.createdAt).toISOString(),
|
|
380
|
+
name: res.name,
|
|
381
|
+
suiteFile: 'suites/smoke.yaml',
|
|
382
|
+
readinessLabels,
|
|
383
|
+
readiness,
|
|
384
|
+
suiteRunnable: res.suiteRunnable,
|
|
385
|
+
replay: { mode: replayMode, results: replayResults, passed: replayPassed, evidence: replayEvidence },
|
|
386
|
+
ciReady,
|
|
387
|
+
ciReadyGate,
|
|
388
|
+
compiledFlows: res.compiledFlows.map((f) => ({
|
|
389
|
+
name: f.name,
|
|
390
|
+
slug: f.slug,
|
|
391
|
+
ok: f.ok,
|
|
392
|
+
errors: f.errors,
|
|
393
|
+
flowPath: f.flowPath ?? null,
|
|
394
|
+
compiledPath: f.compiledPath ?? null,
|
|
395
|
+
})),
|
|
396
|
+
audit,
|
|
397
|
+
variables: res.variables ?? [],
|
|
398
|
+
}, null, 2));
|
|
399
|
+
}
|
|
400
|
+
// Plumb the ACTUAL replay outcome back into the written catalog (review item #5): the suite
|
|
401
|
+
// service writes the catalog before replay (honest `not_replayed`); once we have a real replay
|
|
402
|
+
// result, rewrite the YAML/MD so the committed test cases reflect proven replay status.
|
|
403
|
+
let finalTestCases = res.testCases;
|
|
404
|
+
if (res.manifestPath) {
|
|
405
|
+
const replayStatus = catalogReplayStatus(replayMode, replayResults, replayPassed);
|
|
406
|
+
if (replayStatus !== 'not_replayed') {
|
|
407
|
+
const { pom, flowName } = pomFor(session, name);
|
|
408
|
+
const tc = generateTestCases(pom, {
|
|
409
|
+
appId: appIdOf(session),
|
|
410
|
+
fixtures: session.fixtures,
|
|
411
|
+
notes: secretSafeNotes(session.notes, session.secrets),
|
|
412
|
+
budgetProfile: session.budgetProfile,
|
|
413
|
+
replayStatus,
|
|
414
|
+
});
|
|
415
|
+
writeFiles(session, [
|
|
416
|
+
{ path: `testcases/${flowName}.cases.yaml`, content: tc.yaml },
|
|
417
|
+
{ path: `testcases/${flowName}.cases.md`, content: tc.markdown },
|
|
418
|
+
]);
|
|
419
|
+
finalTestCases = tc.cases;
|
|
353
420
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
421
|
+
}
|
|
422
|
+
const summary = `✅ Suite "${res.name}" generated (${res.pages?.length ?? 0} pages, ${res.testCases?.length ?? 0} test case, suite + audit).\n` +
|
|
423
|
+
`durability: ${audit.durable} durable / ${audit.semi} semi / ${audit.brittle} brittle (${audit.brittlePct}% brittle)` +
|
|
424
|
+
(res.variables?.length ? `\nvariables: ${res.variables.join(', ')}` : '') +
|
|
425
|
+
(audit.brittle ? `\n⚠ ${audit.brittle} brittle locator(s), see locators/locator-audit.json for app-code fixes.` : '') +
|
|
426
|
+
(written.length ? `\nwrote ${written.length} files under ${suiteDir(session)}` : `\n(preview; pass save:true to write)`) +
|
|
427
|
+
(res.compiledFlows.length
|
|
428
|
+
? `\ncompiled ${ranOk}/${res.compiledFlows.length} runnable flow(s) > run: swipium suite run (or qa_flow_run).`
|
|
429
|
+
: `\nNext: qa_flow_compile to produce runnable Flow V2.`) +
|
|
430
|
+
`\nreplay gate (${replayMode}): ${replayPassed ? 'passed' : replayResults.length ? 'not proven' : 'not run'}; readiness=${readiness}; labels=${readinessLabels.join(' > ')}` +
|
|
431
|
+
(ciReady
|
|
432
|
+
? '\nci_ready: compiled + fresh-state replay + state evidence proved'
|
|
433
|
+
: replayMode === 'fresh_state'
|
|
434
|
+
? `\nci_ready missing: ${Object.entries(ciReadyGate)
|
|
435
|
+
.filter(([, ok]) => !ok)
|
|
436
|
+
.map(([k]) => k)
|
|
437
|
+
.join(', ') || 'none'}`
|
|
438
|
+
: '');
|
|
439
|
+
return qaOk({
|
|
440
|
+
name: res.name,
|
|
441
|
+
pages: res.pages,
|
|
442
|
+
audit,
|
|
443
|
+
variables: res.variables,
|
|
444
|
+
testCases: finalTestCases,
|
|
445
|
+
written,
|
|
446
|
+
manifestPath: res.manifestPath ?? null,
|
|
447
|
+
compiledFlows: res.compiledFlows,
|
|
448
|
+
suiteRunnable: res.suiteRunnable,
|
|
449
|
+
replay: { mode: replayMode, results: replayResults, passed: replayPassed, evidence: replayEvidence },
|
|
450
|
+
readiness,
|
|
451
|
+
readinessLabels,
|
|
452
|
+
ciReady,
|
|
453
|
+
ciReadyGate,
|
|
454
|
+
}, summary);
|
|
455
|
+
}
|
|
456
|
+
/** Core handler for qa_generate target:"testcases": industry-style test case catalog from recorded actions. */
|
|
457
|
+
export async function runTestcaseGenerate(sessions, { sessionId, name, format, save }) {
|
|
458
|
+
const session = sessions.get(sessionId);
|
|
459
|
+
if (!session)
|
|
460
|
+
return unknownSessionError(sessionId);
|
|
461
|
+
const noActions = requireActions(session);
|
|
462
|
+
if (noActions)
|
|
463
|
+
return noActions;
|
|
464
|
+
const { pom, flowName } = pomFor(session, name);
|
|
465
|
+
const tc = generateTestCases(pom, {
|
|
466
|
+
appId: appIdOf(session),
|
|
467
|
+
fixtures: session.fixtures,
|
|
468
|
+
notes: secretSafeNotes(session.notes, session.secrets),
|
|
469
|
+
budgetProfile: session.budgetProfile,
|
|
392
470
|
});
|
|
393
|
-
|
|
394
|
-
|
|
471
|
+
const fmt = format ?? 'both';
|
|
472
|
+
const files = [];
|
|
473
|
+
if (fmt !== 'markdown')
|
|
474
|
+
files.push({ path: `testcases/${flowName}.cases.yaml`, content: tc.yaml });
|
|
475
|
+
if (fmt !== 'yaml')
|
|
476
|
+
files.push({ path: `testcases/${flowName}.cases.md`, content: tc.markdown });
|
|
477
|
+
const tcLeak = secretLeakError(session, files);
|
|
478
|
+
if (tcLeak)
|
|
479
|
+
return tcLeak;
|
|
480
|
+
const written = save ? writeFiles(session, files) : [];
|
|
481
|
+
const summary = `Generated ${tc.cases.length} test case(s): ${tc.cases.map((c) => `${c.id} ${c.title}`).join('; ')}` +
|
|
482
|
+
(written.length ? `\nsaved under .swipium/testcases` : '');
|
|
483
|
+
return qaOk({ cases: tc.cases, yaml: fmt !== 'markdown' ? tc.yaml : undefined, markdown: fmt !== 'yaml' ? tc.markdown : undefined, written }, summary);
|
|
484
|
+
}
|
|
485
|
+
export function registerSuite(server, sessions) {
|
|
486
|
+
// ---- qa_flow_compile ----
|
|
487
|
+
server.registerTool('qa_flow_compile', {
|
|
395
488
|
title: 'Compile a POM suite to runnable flows',
|
|
396
|
-
description: 'Compile
|
|
489
|
+
description: 'Compile an existing POM suite on disk (.swipium/suites/<suite>.yaml, e.g. committed or hand-edited) into runnable Flow V2 ' +
|
|
490
|
+
'for qa_flow_run: resolves page-object refs to selectors, carries variables, writes .swipium/flows/<slug>.yaml (+ a copy ' +
|
|
491
|
+
'under .swipium/compiled/), and validates each flow. Needs no session or recorded actions; qa_generate target:"suite" ' +
|
|
492
|
+
'already compiles the suite it generates from a run.',
|
|
397
493
|
inputSchema: {
|
|
398
494
|
sessionId: z.string().optional(),
|
|
399
495
|
projectRoot: z.string().optional(),
|
|
400
496
|
suite: z.string().optional().describe('Suite file relative to .swipium/ (default suites/smoke.yaml).'),
|
|
401
497
|
},
|
|
402
498
|
}, async ({ sessionId, projectRoot, suite }) => {
|
|
403
|
-
let root;
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
499
|
+
let root = sessionId ? sessions.get(sessionId)?.root : undefined;
|
|
500
|
+
if (!root) {
|
|
501
|
+
const resolved = await resolveProjectRoot(server, projectRoot);
|
|
502
|
+
if (!resolved.root)
|
|
503
|
+
return unresolvedProjectRootError(resolved);
|
|
504
|
+
root = resolved.root;
|
|
505
|
+
}
|
|
409
506
|
const result = compileSuite(root, suite ?? 'suites/smoke.yaml');
|
|
410
507
|
if (result.errors.length && result.flows.length === 0) {
|
|
411
|
-
return qaError({
|
|
508
|
+
return qaError({
|
|
509
|
+
what: `Could not compile suite: ${result.errors.join('; ')}`,
|
|
510
|
+
changedState: false,
|
|
511
|
+
retrySafe: true,
|
|
512
|
+
nextSteps: ['Generate a suite first: qa_generate target:"suite", then qa_flow_compile.'],
|
|
513
|
+
});
|
|
412
514
|
}
|
|
413
515
|
const flowsDir = join(root, '.swipium', 'flows');
|
|
414
516
|
const compiledDir = join(root, '.swipium', 'compiled');
|
|
@@ -421,7 +523,7 @@ export function registerSuite(server, sessions) {
|
|
|
421
523
|
let flowPath;
|
|
422
524
|
let compiledPath;
|
|
423
525
|
if (ok) {
|
|
424
|
-
flowPath = join(flowsDir, `${slug}.yaml`); // discoverable by
|
|
526
|
+
flowPath = join(flowsDir, `${slug}.yaml`); // discoverable by qa_flow_run flow:"<slug>"
|
|
425
527
|
compiledPath = join(compiledDir, `${slug}.flow.yaml`);
|
|
426
528
|
writeFileSync(flowPath, f.yaml);
|
|
427
529
|
writeFileSync(compiledPath, f.yaml);
|
|
@@ -430,31 +532,10 @@ export function registerSuite(server, sessions) {
|
|
|
430
532
|
});
|
|
431
533
|
const okCount = compiled.filter((c) => c.ok).length;
|
|
432
534
|
const summary = `Compiled ${okCount}/${compiled.length} flow(s) from ${result.suite}.\n` +
|
|
433
|
-
compiled
|
|
535
|
+
compiled
|
|
536
|
+
.map((c) => ` ${c.ok ? '✓' : '✗'} ${c.name}${c.ok ? ` > flows/${c.slug}.yaml (run: qa_flow_run flow:"${c.slug}")` : `: ${c.errors.join('; ')}`}`)
|
|
537
|
+
.join('\n');
|
|
434
538
|
return qaOk({ suite: result.suite, flows: compiled, okCount }, summary);
|
|
435
539
|
});
|
|
436
|
-
// ---- qa_suite_lint ----
|
|
437
|
-
server.registerTool('qa_suite_lint', {
|
|
438
|
-
title: 'Lint the generated suite',
|
|
439
|
-
description: 'Lint the page objects under .swipium/pages for durability problems: missing durable locators (coordinate-only), copy/locale-fragile text selectors, and dynamic-looking selectors. Reports each with the page/element and the app-code fix. Use after qa_suite_generate, before committing or wiring CI.',
|
|
440
|
-
inputSchema: { sessionId: z.string().optional(), projectRoot: z.string().optional() },
|
|
441
|
-
}, async ({ sessionId, projectRoot }) => {
|
|
442
|
-
let root;
|
|
443
|
-
if (sessionId)
|
|
444
|
-
root = sessions.get(sessionId)?.root;
|
|
445
|
-
root = root ?? projectRoot;
|
|
446
|
-
if (!root)
|
|
447
|
-
return qaError({ what: 'No project root', changedState: false, retrySafe: true, nextSteps: ['Pass sessionId or projectRoot.'] });
|
|
448
|
-
const findings = lintSuitePages(root);
|
|
449
|
-
if (!findings.exists) {
|
|
450
|
-
return qaError({ what: 'No .swipium/pages to lint', changedState: false, retrySafe: true, nextSteps: ['Generate a suite first: qa_suite_generate.'] });
|
|
451
|
-
}
|
|
452
|
-
const errors = findings.items.filter((f) => f.severity === 'error');
|
|
453
|
-
const summary = `Linted ${findings.pageCount} page object(s): ${findings.items.length} issue(s) (${errors.length} brittle).\n` +
|
|
454
|
-
(findings.items.length
|
|
455
|
-
? findings.items.slice(0, 20).map((i) => ` ${i.severity === 'error' ? '✗' : '⚠'} ${i.page}.${i.element}: ${i.message}`).join('\n')
|
|
456
|
-
: ' ✓ all locators are durable.');
|
|
457
|
-
return qaOk({ pageCount: findings.pageCount, items: findings.items }, summary);
|
|
458
|
-
});
|
|
459
540
|
}
|
|
460
541
|
//# sourceMappingURL=suite.js.map
|