swipium 1.5.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 +171 -0
- package/README.md +173 -280
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +30 -27
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +47 -21
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +2 -2
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +3 -3
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +2 -2
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +2 -2
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/issues.js +10 -10
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +5 -5
- 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 +77 -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 +2 -2
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +3 -3
- 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 +1 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +17 -17
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +55 -7
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +3 -3
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +5 -5
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +10 -10
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +3 -3
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +1 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +7 -7
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +3 -3
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +14 -25
- 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 +5 -5
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +2 -2
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +13 -7
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +5 -5
- 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 +265 -106
- 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 +71 -26
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +276 -105
- 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/{tools/automationGenerate.js → automationGen/run.js} +72 -113
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +6 -4
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +20 -8
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +2 -2
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +7 -7
- 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 +35 -14
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +6 -6
- 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 +45 -14
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +6 -6
- 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 +3 -3
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +65 -62
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +300 -36
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +120 -7
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +350 -50
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +2 -2
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +5 -5
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/policy.js +5 -5
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +301 -284
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +1 -1
- package/dist/featureTesting/executionBootstrap.js +29 -30
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +2 -2
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +12 -12
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +7 -7
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +10 -10
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +6 -6
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -4
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +1 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +1 -1
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +11 -11
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +3 -3
- 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 +5 -5
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +11 -11
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +8 -8
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +9 -9
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +5 -5
- 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 +33 -3
- 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 +20 -10
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/pack.js +3 -3
- 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 +111 -23
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +153 -57
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +50 -5
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +4 -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/issues/classify.js +16 -16
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +24 -4
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +94 -31
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +4 -4
- 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 +4 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +4 -4
- 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 +75 -7
- 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 +32 -7
- 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 +1 -1
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +370 -31
- package/dist/lib/lockfile.js.map +1 -1
- package/dist/lib/needsInput.js +3 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -2
- 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 +79 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +3 -3
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -2
- 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 +52 -10
- 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 +294 -15
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/checks.js +12 -12
- 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 +1 -1
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +3 -3
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +72 -18
- 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 +219 -36
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +63 -17
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +13 -13
- 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 +4 -4
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +62 -21
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +119 -30
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js +162 -73
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +34 -16
- package/dist/orchestration/testThis/terminal.js.map +1 -1
- package/dist/orchestration/testThis/types.js +1 -1
- package/dist/orchestration/testThis/types.js.map +1 -1
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +22 -22
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +392 -37
- 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/policy.js +7 -4
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +8 -8
- package/dist/report/qaLevel.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/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +472 -45
- 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 +2 -2
- package/dist/services/build.js.map +1 -1
- package/dist/{tools → services}/flowGenerate.js +29 -13
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +4 -4
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -8
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +24 -9
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +100 -45
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +32 -16
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +28 -3
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +40 -27
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +270 -6
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +317 -48
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/session/progress.js +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +275 -48
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +53 -8
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +28 -4
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +11 -5
- 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 +46 -2
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +9 -5
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +4 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +50 -41
- 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 +9 -7
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +4 -4
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +13 -9
- 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 +5 -5
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +5 -5
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +3 -3
- 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 +794 -164
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +451 -128
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +69 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +188 -130
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +32 -42
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +21 -31
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +55 -12
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +76 -24
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +68 -28
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +41 -47
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +31 -48
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +34 -41
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +183 -103
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +25 -13
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +52 -89
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +16 -5
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +15 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +45 -223
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +251 -35
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +41 -16
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +36 -35
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +50 -45
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +31 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +26 -26
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +10 -16
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +117 -50
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +11 -22
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +11 -15
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +78 -24
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +23 -21
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -16
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +20 -16
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +155 -118
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +104 -116
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +72 -43
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +30 -20
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +24 -28
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +686 -210
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -38
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +187 -57
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +31 -22
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +53 -6
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +80 -12
- 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 +194 -128
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +812 -152
- package/package.json +3 -2
- package/dist/tools/assertVisual.js +0 -84
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -179
- 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.map +0 -1
- package/dist/tools/history.js +0 -86
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/locator.js +0 -85
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/permissions.js +0 -174
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -52
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -76
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -133
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -217
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -106
- package/dist/tools/visualText.js.map +0 -1
package/dist/tools/flow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// qa_flow_check + qa_flow_run
|
|
1
|
+
// qa_flow_check + qa_flow_run: turn exploration into repeatable QA.
|
|
2
2
|
// Flows live as .swipium/flows/*.yaml. check = parse + static validation (no device).
|
|
3
3
|
// run (mode:"run", default) = the orchestrator (src/flows/run.ts), reporting the exact failing
|
|
4
4
|
// step + evidence. run (mode:"plan", 1.5.0 consolidation) = read-only execution preview:
|
|
@@ -7,11 +7,14 @@ import { z } from 'zod';
|
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
import { existsSync, readFileSync } from 'node:fs';
|
|
9
9
|
import { join, isAbsolute } from 'node:path';
|
|
10
|
-
import { qaOk, qaError, qaStop, qaAnnotate } from '../lib/result.js';
|
|
11
|
-
import { parseFlow } from '../flows/schema.js';
|
|
10
|
+
import { qaOk, qaError, qaStop, qaAnnotate, unknownSessionError } from '../lib/result.js';
|
|
11
|
+
import { FLOW_ENV_PREFIX, isMutatingFlowStep, lookupFlowVar, parseFlow, SECRET_VAR_NAME, } from '../flows/schema.js';
|
|
12
|
+
import { seedExactCommand } from '../flows/seedExec.js';
|
|
13
|
+
import { withinRootOrNull } from '../flows/paths.js';
|
|
14
|
+
import { makeRedactor } from '../lib/redact.js';
|
|
12
15
|
import { lintFlowObjectWithOptions } from '../flows/lint.js';
|
|
13
16
|
import { runFlow } from '../flows/run.js';
|
|
14
|
-
import { getDriver } from '../session/attach.js';
|
|
17
|
+
import { blockedDeviceResult, getDriver } from '../session/attach.js';
|
|
15
18
|
import { loadPolicy } from '../report/policy.js';
|
|
16
19
|
import { classifyFlakeResults } from '../report/flake.js';
|
|
17
20
|
import { validateCiMutationPolicy, validateCiVariables } from '../ci/preflight.js';
|
|
@@ -23,14 +26,15 @@ import { resolveMaskProvider, resolveVisualProvider } from '../visual/provider.j
|
|
|
23
26
|
import { backendCapabilities, backendForDriverKind } from '../automation/capabilities.js';
|
|
24
27
|
import { compileAutomationPlan } from '../automation/plan.js';
|
|
25
28
|
import { buildReadiness } from '../automation/report.js';
|
|
29
|
+
import { resolveProjectRoot } from '../context/projectRoot.js';
|
|
26
30
|
const ALL_BACKENDS = ['android-direct', 'ios-raw-simulator', 'ios-wda', 'appium-uiautomator2', 'appium-xcuitest'];
|
|
27
31
|
const STATUS_ICON = { ready: '✅', candidate: '🟡', blocked: '⛔' };
|
|
28
32
|
/** Resolve a flow's YAML from explicit text, an absolute/relative path, or a name under .swipium/flows. */
|
|
29
|
-
function loadFlowSource(root, flow, flowYaml) {
|
|
33
|
+
export function loadFlowSource(root, flow, flowYaml) {
|
|
30
34
|
if (flowYaml && flowYaml.trim())
|
|
31
35
|
return { yamlText: flowYaml, source: 'inline' };
|
|
32
36
|
if (!flow)
|
|
33
|
-
return { error: 'Provide a flow name, a path, or flowYaml.' };
|
|
37
|
+
return { error: 'Provide a flow name, a path, or flowYaml.', errorCode: 'INVALID_ARGUMENT' };
|
|
34
38
|
const candidates = [];
|
|
35
39
|
if (isAbsolute(flow))
|
|
36
40
|
candidates.push(flow);
|
|
@@ -45,13 +49,39 @@ function loadFlowSource(root, flow, flowYaml) {
|
|
|
45
49
|
return { yamlText: readFileSync(p, 'utf8'), source: p };
|
|
46
50
|
}
|
|
47
51
|
catch (e) {
|
|
48
|
-
return { error: `Could not read ${p}: ${String(e)}
|
|
52
|
+
return { error: `Could not read ${p}: ${String(e)}`, errorCode: 'INVALID_FLOW' };
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
error: `Flow not found. Looked for: ${candidates.join(', ') || '(no project root; pass projectRoot or set SWIPIUM_PROJECT_ROOT)'}`,
|
|
58
|
+
errorCode: 'FLOW_NOT_FOUND',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The project root a flow tool reads from: explicit projectRoot arg > the session's root >
|
|
63
|
+
* resolveProjectRoot (MCP roots > SWIPIUM_PROJECT_ROOT/CLAUDE_PROJECT_DIR > cwd marker), like
|
|
64
|
+
* every other root-aware tool. Undefined only when nothing resolves (inline flowYaml still works).
|
|
65
|
+
*/
|
|
66
|
+
async function flowRoot(server, sessionRoot, projectRoot) {
|
|
67
|
+
if (!projectRoot && sessionRoot)
|
|
68
|
+
return sessionRoot;
|
|
69
|
+
const resolved = await resolveProjectRoot(server, projectRoot);
|
|
70
|
+
return resolved.root ?? sessionRoot;
|
|
71
|
+
}
|
|
72
|
+
function flowSourceError(src) {
|
|
73
|
+
return qaError({
|
|
74
|
+
what: src.error ?? 'Flow could not be loaded',
|
|
75
|
+
changedState: false,
|
|
76
|
+
retrySafe: true,
|
|
77
|
+
failureCode: src.errorCode ?? 'FLOW_NOT_FOUND',
|
|
78
|
+
nextSteps: [
|
|
79
|
+
src.errorCode === 'INVALID_ARGUMENT'
|
|
80
|
+
? 'Pass flow="<name>" (under .swipium/flows), a .yaml path, or inline flowYaml.'
|
|
81
|
+
: 'Pass an existing flow name/path or inline flowYaml; pass projectRoot (or set SWIPIUM_PROJECT_ROOT) if the flow lives in another project.',
|
|
82
|
+
],
|
|
83
|
+
});
|
|
53
84
|
}
|
|
54
|
-
const MUTATING_FLOW_STEPS = new Set(['networkOffline', 'networkOnline', 'seed', 'restartApp']);
|
|
55
85
|
function stable(value) {
|
|
56
86
|
if (Array.isArray(value))
|
|
57
87
|
return value.map(stable);
|
|
@@ -72,15 +102,41 @@ function hash(value) {
|
|
|
72
102
|
function allFlowSteps(flow) {
|
|
73
103
|
return [...flow.setup, ...flow.steps, ...flow.teardown];
|
|
74
104
|
}
|
|
75
|
-
|
|
105
|
+
/** Where a seed's command came from, stated in consent: fixtures are loaded from the repo's
|
|
106
|
+
* .swipium/fixtures.json (or qa_start_session fixtures). Swipium never reviewed them. */
|
|
107
|
+
const SEED_ORIGIN = 'declared fixture (.swipium/fixtures.json or qa_start_session fixtures), repo-supplied, UNREVIEWED';
|
|
108
|
+
/** `${VAR}` URL preview for consent: the URL as it will be opened, with credential-like variable
|
|
109
|
+
* values and registered session secrets masked, plus where each variable is read from. */
|
|
110
|
+
function openUrlPreview(url, vars, secrets) {
|
|
111
|
+
const variables = [];
|
|
112
|
+
const resolved = url.replace(/\$\{([^}]+)\}/g, (_, name) => {
|
|
113
|
+
const v = lookupFlowVar(name, vars);
|
|
114
|
+
variables.push({ name, source: v == null ? 'missing' : vars[name] != null ? 'variables' : 'env' });
|
|
115
|
+
if (v == null)
|
|
116
|
+
return `\${${name}}`;
|
|
117
|
+
return SECRET_VAR_NAME.test(name) ? `«${name}»` : v;
|
|
118
|
+
});
|
|
119
|
+
return { url, resolvedUrl: makeRedactor(secrets)(resolved) ?? resolved, variables };
|
|
120
|
+
}
|
|
121
|
+
function flowMutationAffects(flow, session, source, repeat, externalProviders = [], vars = {}) {
|
|
76
122
|
const mutations = allFlowSteps(flow)
|
|
77
123
|
.map((step, index) => {
|
|
78
|
-
if (!
|
|
124
|
+
if (!isMutatingFlowStep(step))
|
|
79
125
|
return null;
|
|
80
126
|
if (step.kind === 'seed') {
|
|
81
|
-
const
|
|
82
|
-
return {
|
|
127
|
+
const seed = session.fixtures.find((f) => f.name === step.fixture)?.seed;
|
|
128
|
+
return {
|
|
129
|
+
step: index + 1,
|
|
130
|
+
kind: step.kind,
|
|
131
|
+
fixture: step.fixture,
|
|
132
|
+
seedType: seed?.type ?? 'unknown',
|
|
133
|
+
// The EXACT argv / URL the seed will run. Consent must show what executes.
|
|
134
|
+
command: seed ? seedExactCommand(seed) : null,
|
|
135
|
+
origin: SEED_ORIGIN,
|
|
136
|
+
};
|
|
83
137
|
}
|
|
138
|
+
if (step.kind === 'openUrl')
|
|
139
|
+
return { step: index + 1, kind: step.kind, ...openUrlPreview(step.url, vars, session.secrets ?? []) };
|
|
84
140
|
return { step: index + 1, kind: step.kind };
|
|
85
141
|
})
|
|
86
142
|
.filter((x) => !!x);
|
|
@@ -94,11 +150,21 @@ function flowMutationAffects(flow, session, source, repeat, externalProviders =
|
|
|
94
150
|
externalProviders,
|
|
95
151
|
};
|
|
96
152
|
}
|
|
153
|
+
/** One line per mutating step for the consent's exactCommand (seeds show their argv/URL). */
|
|
154
|
+
function mutationCommandLines(affects) {
|
|
155
|
+
return affects.mutations.map((m) => {
|
|
156
|
+
if (m.kind === 'seed' && 'fixture' in m)
|
|
157
|
+
return `${m.step}:seed ${m.fixture} [${m.seedType}] > ${m.command ?? '(no seed spec, step will fail)'} (repo-supplied, unreviewed)`;
|
|
158
|
+
if (m.kind === 'openUrl' && 'resolvedUrl' in m)
|
|
159
|
+
return `${m.step}:openUrl ${m.resolvedUrl} (variables: ${m.variables.map((v) => `${v.name}←${v.source}`).join(', ')})`;
|
|
160
|
+
return `${m.step}:${m.kind}`;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
97
163
|
function flowMutationRisk(affects) {
|
|
98
164
|
if (affects.mutations.some((m) => m.kind === 'seed' && m.seedType === 'script'))
|
|
99
165
|
return 'high';
|
|
100
166
|
if (affects.externalProviders.length ||
|
|
101
|
-
affects.mutations.some((m) => m.kind === 'networkOffline' || m.kind === 'networkOnline' || m.kind === 'seed'))
|
|
167
|
+
affects.mutations.some((m) => m.kind === 'networkOffline' || m.kind === 'networkOnline' || m.kind === 'seed' || m.kind === 'openUrl'))
|
|
102
168
|
return 'medium';
|
|
103
169
|
return 'low';
|
|
104
170
|
}
|
|
@@ -111,39 +177,46 @@ function ocrSteps(flow) {
|
|
|
111
177
|
});
|
|
112
178
|
return out;
|
|
113
179
|
}
|
|
180
|
+
const LOCATOR_FAILURES = new Set(['ELEMENT_NOT_FOUND', 'AMBIGUOUS_SELECTOR', 'STALE_REF', 'INVALID_SELECTOR', 'ASSERTION_FAILED']);
|
|
181
|
+
/** nextSteps for a failed qa_flow_run: point at qa_flow_repair for the failing step. */
|
|
182
|
+
export function failedFlowNextSteps(name, flowArg, failedAtStep, failureCode) {
|
|
183
|
+
const out = [];
|
|
184
|
+
// A missing variable is not locator drift: qa_flow_repair cannot fix it (and a cancelled run is
|
|
185
|
+
// not a failure to repair).
|
|
186
|
+
if (failedAtStep != null && failureCode !== 'MISSING_FIXTURE' && failureCode !== 'CANCELLED') {
|
|
187
|
+
const target = flowArg ? `flow:"${flowArg}"` : 'flowYaml:<same YAML>';
|
|
188
|
+
out.push(`${failureCode && !LOCATOR_FAILURES.has(failureCode) ? 'If this is locator drift (renamed/moved control), ' : ''}call qa_flow_repair { ${target}, failedStep:${failedAtStep} } for a reviewable locator fix (review before apply).`);
|
|
189
|
+
}
|
|
190
|
+
if (failureCode === 'MISSING_FIXTURE')
|
|
191
|
+
out.push(`Pass missing values via qa_flow_run { variables } (flows read process.env only for ${FLOW_ENV_PREFIX}* names).`);
|
|
192
|
+
out.push(`If the app itself is wrong, record it with qa_note { workflow:"${name}", outcome:"fail" } with the step evidence.`);
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
114
195
|
export function registerFlow(server, sessions) {
|
|
115
196
|
server.registerTool('qa_flow_check', {
|
|
116
197
|
title: 'Check a flow',
|
|
117
|
-
description: '
|
|
198
|
+
description: 'Statically validate a flow (.swipium/flows/*.yaml: name + steps) without running it: syntax/schema errors with the offending ' +
|
|
199
|
+
'step, plus warnings. Pass flow (name or path) or flowYaml. For an execution preview per backend use qa_flow_run mode:"plan".',
|
|
118
200
|
inputSchema: {
|
|
119
201
|
sessionId: z.string().optional(),
|
|
202
|
+
projectRoot: z.string().optional().describe('Absolute app root (default: session root > MCP roots > env > cwd).'),
|
|
120
203
|
flow: z.string().optional().describe('Flow name under .swipium/flows, or a path to a .yaml file.'),
|
|
121
204
|
flowYaml: z.string().optional().describe('Inline flow YAML (instead of a file).'),
|
|
122
|
-
platform: z
|
|
123
|
-
|
|
124
|
-
.optional()
|
|
125
|
-
.describe('Optional authoring target for platform-aware warnings.'),
|
|
126
|
-
ci: z
|
|
127
|
-
.boolean()
|
|
128
|
-
.optional()
|
|
129
|
-
.describe('When true, add CI preflight warnings such as missing variables and mutating steps not allowed by policy.'),
|
|
205
|
+
platform: z.enum(['android', 'ios', 'cross-platform']).optional().describe('Authoring target for platform-aware warnings.'),
|
|
206
|
+
ci: z.boolean().optional().describe('Add CI preflight warnings (missing variables, mutating steps policy forbids).'),
|
|
130
207
|
},
|
|
131
|
-
}, async ({ sessionId, flow, flowYaml, platform, ci }) => {
|
|
132
|
-
const root = sessionId ? sessions.get(sessionId)?.root : undefined;
|
|
208
|
+
}, async ({ sessionId, projectRoot, flow, flowYaml, platform, ci }) => {
|
|
209
|
+
const root = await flowRoot(server, sessionId ? sessions.get(sessionId)?.root : undefined, projectRoot);
|
|
133
210
|
const src = loadFlowSource(root, flow, flowYaml);
|
|
134
211
|
if (src.error)
|
|
135
|
-
return
|
|
136
|
-
what: src.error,
|
|
137
|
-
changedState: false,
|
|
138
|
-
retrySafe: true,
|
|
139
|
-
nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
|
|
140
|
-
});
|
|
212
|
+
return flowSourceError(src);
|
|
141
213
|
const { flow: parsed, errors } = parseFlow(src.yamlText);
|
|
142
214
|
if (errors.length || !parsed) {
|
|
143
215
|
return qaError({
|
|
144
216
|
what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'})`,
|
|
145
217
|
changedState: false,
|
|
146
218
|
retrySafe: true,
|
|
219
|
+
failureCode: 'INVALID_FLOW',
|
|
147
220
|
nextSteps: ['Fix the listed errors and re-check.'],
|
|
148
221
|
}, { source: src.source, errors });
|
|
149
222
|
}
|
|
@@ -151,11 +224,11 @@ export function registerFlow(server, sessions) {
|
|
|
151
224
|
const STRUCTURED_KINDS = new Set(['tap', 'assertVisible', 'assertNotVisible', 'scrollTo', 'waitForVisible', 'inputText']);
|
|
152
225
|
const warnings = [];
|
|
153
226
|
if (!parsed.appId)
|
|
154
|
-
warnings.push("No appId
|
|
227
|
+
warnings.push("No appId, so a prepareTarget step will rely on the session's prepared appId.");
|
|
155
228
|
if (allSteps.some((s) => s.kind === 'tap' && s.selector.startsWith('@')))
|
|
156
|
-
warnings.push('Uses @ref selectors
|
|
229
|
+
warnings.push('Uses @ref selectors. Refs are run-time only; prefer text/id selectors for durable flows.');
|
|
157
230
|
if (src.yamlText.includes('${'))
|
|
158
|
-
warnings.push(
|
|
231
|
+
warnings.push(`Uses \${VARIABLES}. Provide them via qa_flow_run { variables }, stored session inputs, or ${FLOW_ENV_PREFIX}* environment variables (other env names are never read).`);
|
|
159
232
|
const flowOcrSteps = ocrSteps(parsed);
|
|
160
233
|
if (flowOcrSteps.length) {
|
|
161
234
|
warnings.push(`Uses OCR visual-provider steps (${flowOcrSteps.map((s) => `${s.step}:${s.kind}`).join(', ')}); qa_flow_run will require provider consent and a configured ocrCommand.`);
|
|
@@ -167,10 +240,10 @@ export function registerFlow(server, sessions) {
|
|
|
167
240
|
const inferredPlatform = platform ?? (driverKind === 'simulator' || driverKind === 'wda' ? 'ios' : driverKind === 'direct' ? 'android' : undefined);
|
|
168
241
|
const usesStructured = allSteps.some((s) => STRUCTURED_KINDS.has(s.kind));
|
|
169
242
|
if (driverKind === 'simulator' && parsed.mode === 'structured') {
|
|
170
|
-
warnings.push('mode:structured on the iOS simulator backend
|
|
243
|
+
warnings.push('mode:structured on the iOS simulator backend. tap/assertVisible/inputText need a UI tree (unavailable). Use mode: visual with image/visual steps.');
|
|
171
244
|
}
|
|
172
245
|
if (parsed.mode === 'visual' && usesStructured) {
|
|
173
|
-
warnings.push('mode:visual but the flow uses structured steps (tap/assertVisible/…)
|
|
246
|
+
warnings.push('mode:visual but the flow uses structured steps (tap/assertVisible/…). Those need a UI tree and will fail on a visual-only screen.');
|
|
174
247
|
}
|
|
175
248
|
for (const s of allSteps) {
|
|
176
249
|
const text = s.kind === 'tap'
|
|
@@ -212,8 +285,10 @@ export function registerFlow(server, sessions) {
|
|
|
212
285
|
if (root) {
|
|
213
286
|
for (const s of allSteps) {
|
|
214
287
|
if (s.kind === 'tapImage' || s.kind === 'assertImage') {
|
|
215
|
-
const p =
|
|
216
|
-
if (!
|
|
288
|
+
const p = withinRootOrNull(root, s.template);
|
|
289
|
+
if (!p)
|
|
290
|
+
warnings.push(`image template outside the project root: ${s.template}; qa_flow_run will refuse it.`);
|
|
291
|
+
else if (!existsSync(p))
|
|
217
292
|
warnings.push(`image template not found: ${s.template} (resolve relative to the project root).`);
|
|
218
293
|
}
|
|
219
294
|
}
|
|
@@ -231,45 +306,35 @@ export function registerFlow(server, sessions) {
|
|
|
231
306
|
fixtures: parsed.fixtures,
|
|
232
307
|
warnings,
|
|
233
308
|
lintFindings,
|
|
234
|
-
}, `✅ flow "${parsed.name}" is valid
|
|
309
|
+
}, `✅ flow "${parsed.name}" is valid: ${parsed.steps.length} steps (mode=${parsed.mode}${parsed.setup.length ? `, ${parsed.setup.length} setup` : ''}${parsed.teardown.length ? `, ${parsed.teardown.length} teardown` : ''})${warnings.length ? `\nwarnings:\n - ${warnings.join('\n - ')}` : ''}`);
|
|
235
310
|
});
|
|
236
311
|
server.registerTool('qa_flow_run', {
|
|
237
312
|
title: 'Run a flow (or preview its execution plan)',
|
|
238
|
-
description: 'Run a
|
|
313
|
+
description: 'Run a flow on the prepared app (mode:"run", default) or preview it without a device (mode:"plan"). run executes steps ' +
|
|
314
|
+
'server-side with setup/teardown, fail-fast, no auto-retry of mutating steps (consent-gated, like OCR steps); a failure ' +
|
|
315
|
+
'returns the step, screenshot, failureCode, and health. plan reports, per backend (Android, iOS simulator, iOS WDA, ' +
|
|
316
|
+
'Appium), whether each step is native / fallback / visual_only / unsupported. Pass flow (name/path) or flowYaml, plus ' +
|
|
317
|
+
'variables for ${VAR}. Steps: docs/tools.md#flow-steps.',
|
|
239
318
|
inputSchema: {
|
|
240
|
-
mode: z
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
.describe("run (default): execute the flow on the session's prepared device. plan: READ-ONLY execution preview — no device is touched; compiles the flow against backend capabilities (Android direct, iOS raw simulator, iOS WDA, Appium UiAutomator2/XCUITest) and reports what would run, what falls back, and what is blocked."),
|
|
244
|
-
sessionId: z
|
|
319
|
+
mode: z.enum(['plan', 'run']).optional(),
|
|
320
|
+
sessionId: z.string().optional().describe('Required for run; plan: resolves names + marks the attached backend.'),
|
|
321
|
+
projectRoot: z
|
|
245
322
|
.string()
|
|
246
323
|
.optional()
|
|
247
|
-
.describe('
|
|
248
|
-
flow: z.string().optional().describe('Flow name under .swipium/flows, or a
|
|
249
|
-
flowYaml: z.string().optional().describe('Inline flow YAML
|
|
324
|
+
.describe('plan: absolute app root for flow names (default: session root > MCP roots > env > cwd).'),
|
|
325
|
+
flow: z.string().optional().describe('Flow name under .swipium/flows, or a .yaml path.'),
|
|
326
|
+
flowYaml: z.string().optional().describe('Inline flow YAML.'),
|
|
250
327
|
variables: z
|
|
251
328
|
.record(z.string())
|
|
252
329
|
.optional()
|
|
253
|
-
.describe('
|
|
254
|
-
repeat: z
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
.min(1)
|
|
258
|
-
.max(10)
|
|
259
|
-
.optional()
|
|
260
|
-
.describe('(mode:"run" only) Run the flow N times for flake detection (default 1). Reports pass-rate + a deterministic-pass | deterministic-fail | flaky classification.'),
|
|
261
|
-
consentId: z
|
|
262
|
-
.string()
|
|
263
|
-
.optional()
|
|
264
|
-
.describe('(mode:"run" only) Consent token returned when the flow contains mutating steps or external visual-provider steps such as OCR.'),
|
|
265
|
-
approve: z
|
|
266
|
-
.boolean()
|
|
267
|
-
.optional()
|
|
268
|
-
.describe('(mode:"run" only) Set true with consentId to execute after reviewing the exact flow hash, mutation list, and external provider command if present.'),
|
|
330
|
+
.describe('run: ${VAR} values (win over stored session inputs, then SWIPIUM_* env only); credential-like names redacted.'),
|
|
331
|
+
repeat: z.number().int().min(1).max(10).optional().describe('run: repeat N times for flake classification (default 1).'),
|
|
332
|
+
consentId: z.string().optional().describe('run: consent for mutating / OCR steps.'),
|
|
333
|
+
approve: z.boolean().optional(),
|
|
269
334
|
backend: z
|
|
270
335
|
.enum(['android-direct', 'ios-raw-simulator', 'ios-wda', 'appium-uiautomator2', 'appium-xcuitest'])
|
|
271
336
|
.optional()
|
|
272
|
-
.describe('
|
|
337
|
+
.describe('plan: only this backend (default all five).'),
|
|
273
338
|
appium: z
|
|
274
339
|
.object({
|
|
275
340
|
automationName: z.string().optional(),
|
|
@@ -277,12 +342,12 @@ export function registerFlow(server, sessions) {
|
|
|
277
342
|
webviewContextsAvailable: z.boolean().optional(),
|
|
278
343
|
})
|
|
279
344
|
.optional()
|
|
280
|
-
.describe('
|
|
345
|
+
.describe('plan: Appium session hints.'),
|
|
281
346
|
},
|
|
282
|
-
}, async ({ mode, sessionId, flow, flowYaml, variables, repeat, consentId, approve, backend, appium }) => {
|
|
347
|
+
}, async ({ mode, sessionId, projectRoot, flow, flowYaml, variables, repeat, consentId, approve, backend, appium }) => {
|
|
283
348
|
const effectiveMode = mode ?? 'run';
|
|
284
349
|
const notes = [];
|
|
285
|
-
// ---- mode:"plan"
|
|
350
|
+
// ---- mode:"plan": read-only execution preview (merged twin, 1.5.0). ----
|
|
286
351
|
if (effectiveMode === 'plan') {
|
|
287
352
|
const ignored = [
|
|
288
353
|
variables !== undefined && 'variables',
|
|
@@ -291,30 +356,26 @@ export function registerFlow(server, sessions) {
|
|
|
291
356
|
approve !== undefined && 'approve',
|
|
292
357
|
].filter((x) => !!x);
|
|
293
358
|
if (ignored.length)
|
|
294
|
-
notes.push(`ignored parameter(s) not applicable to mode:"plan": ${ignored.join(', ')}
|
|
359
|
+
notes.push(`ignored parameter(s) not applicable to mode:"plan": ${ignored.join(', ')}; re-run with mode:"run" to execute`);
|
|
295
360
|
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
296
|
-
const root = session?.root;
|
|
361
|
+
const root = await flowRoot(server, session?.root, projectRoot);
|
|
297
362
|
const src = loadFlowSource(root, flow, flowYaml);
|
|
298
363
|
if (src.error)
|
|
299
|
-
return qaAnnotate(
|
|
300
|
-
what: src.error,
|
|
301
|
-
changedState: false,
|
|
302
|
-
retrySafe: true,
|
|
303
|
-
nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
|
|
304
|
-
}), notes);
|
|
364
|
+
return qaAnnotate(flowSourceError(src), notes);
|
|
305
365
|
const { flow: parsed, errors } = parseFlow(src.yamlText);
|
|
306
366
|
if (errors.length || !parsed) {
|
|
307
367
|
return qaAnnotate(qaError({
|
|
308
|
-
what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'})
|
|
368
|
+
what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'}), run qa_flow_check`,
|
|
309
369
|
changedState: false,
|
|
310
370
|
retrySafe: true,
|
|
371
|
+
failureCode: 'INVALID_FLOW',
|
|
311
372
|
nextSteps: ['Fix the listed errors and re-plan.'],
|
|
312
373
|
}, { source: src.source, errors }), notes);
|
|
313
374
|
}
|
|
314
375
|
const appiumHints = appium ? { ...appium } : undefined;
|
|
315
376
|
const attachedBackend = session?.driver?.kind ? backendForDriverKind(session.driver.kind, appiumHints) : undefined;
|
|
316
377
|
// Which backends to plan for: an explicit one, else all concrete backends (so the answer covers
|
|
317
|
-
// Android + iOS even with no device attached
|
|
378
|
+
// Android + iOS even with no device attached: the pre-run "can this run?" product question).
|
|
318
379
|
const targets = backend ? [backend] : [...ALL_BACKENDS];
|
|
319
380
|
if (attachedBackend && attachedBackend !== 'unknown' && !targets.includes(attachedBackend))
|
|
320
381
|
targets.unshift(attachedBackend);
|
|
@@ -346,19 +407,28 @@ export function registerFlow(server, sessions) {
|
|
|
346
407
|
};
|
|
347
408
|
});
|
|
348
409
|
const summaryLines = plans.map((p) => {
|
|
349
|
-
const head = `${STATUS_ICON[p.status] ?? '•'} ${p.backend}${p.attached ? ' (attached)' : ''}: ${p.status}
|
|
410
|
+
const head = `${STATUS_ICON[p.status] ?? '•'} ${p.backend}${p.attached ? ' (attached)' : ''}: ${p.status}, ${p.supportedSteps} supported, ${p.unsupportedSteps} unsupported`;
|
|
350
411
|
return `${head}\n ${p.agentMessage}`;
|
|
351
412
|
});
|
|
352
|
-
const summary = `flow "${parsed.name}"
|
|
413
|
+
const summary = `flow "${parsed.name}": automation plan across ${plans.length} backend(s) (mode:"plan", read-only, nothing executed):\n${summaryLines.join('\n')}`;
|
|
353
414
|
return qaAnnotate(qaOk({ flow: parsed.name, appId: parsed.appId ?? null, mode: parsed.mode, source: src.source, plans }, summary), notes);
|
|
354
415
|
}
|
|
355
|
-
// ---- mode:"run"
|
|
356
|
-
const runIgnored = [
|
|
416
|
+
// ---- mode:"run": execute on the prepared device. ----
|
|
417
|
+
const runIgnored = [
|
|
418
|
+
backend !== undefined && 'backend',
|
|
419
|
+
appium !== undefined && 'appium',
|
|
420
|
+
projectRoot !== undefined && 'projectRoot',
|
|
421
|
+
].filter((x) => !!x);
|
|
357
422
|
if (runIgnored.length)
|
|
358
|
-
notes.push(`ignored parameter(s) not applicable to mode:"run": ${runIgnored.join(', ')}
|
|
423
|
+
notes.push(`ignored parameter(s) not applicable to mode:"run": ${runIgnored.join(', ')}; they refine the mode:"plan" preview`);
|
|
359
424
|
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
360
|
-
|
|
425
|
+
if (sessionId && !session)
|
|
426
|
+
return qaAnnotate(unknownSessionError(sessionId), notes);
|
|
427
|
+
const { driver, blocked } = session ? await getDriver(session) : { driver: undefined, blocked: undefined };
|
|
361
428
|
if (!session || !driver) {
|
|
429
|
+
const refused = blockedDeviceResult(blocked);
|
|
430
|
+
if (refused)
|
|
431
|
+
return qaAnnotate(refused, notes);
|
|
362
432
|
return qaAnnotate(qaError({
|
|
363
433
|
what: sessionId
|
|
364
434
|
? 'No device attached to this session'
|
|
@@ -370,22 +440,18 @@ export function registerFlow(server, sessions) {
|
|
|
370
440
|
}
|
|
371
441
|
const src = loadFlowSource(session.root, flow, flowYaml);
|
|
372
442
|
if (src.error)
|
|
373
|
-
return qaAnnotate(
|
|
374
|
-
what: src.error,
|
|
375
|
-
changedState: false,
|
|
376
|
-
retrySafe: true,
|
|
377
|
-
nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
|
|
378
|
-
}), notes);
|
|
443
|
+
return qaAnnotate(flowSourceError(src), notes);
|
|
379
444
|
const { flow: parsed, errors } = parseFlow(src.yamlText);
|
|
380
445
|
if (errors.length || !parsed) {
|
|
381
446
|
return qaAnnotate(qaError({
|
|
382
|
-
what: `Flow is invalid
|
|
447
|
+
what: `Flow is invalid. Run qa_flow_check first`,
|
|
383
448
|
changedState: false,
|
|
384
449
|
retrySafe: true,
|
|
450
|
+
failureCode: 'INVALID_FLOW',
|
|
385
451
|
nextSteps: ['Fix the flow and re-run.'],
|
|
386
452
|
}, { errors }), notes);
|
|
387
453
|
}
|
|
388
|
-
// Backend/mode gate (
|
|
454
|
+
// Backend/mode gate (catch unsupported combos before running). A structured-mode
|
|
389
455
|
// flow needs a UI tree, which the iOS simulator and a visual-fallback session don't have.
|
|
390
456
|
if (parsed.mode === 'structured' && (driver.kind === 'simulator' || session.mode === 'visual-fallback')) {
|
|
391
457
|
return qaAnnotate(qaError({
|
|
@@ -449,7 +515,14 @@ export function registerFlow(server, sessions) {
|
|
|
449
515
|
}), notes);
|
|
450
516
|
}
|
|
451
517
|
}
|
|
452
|
-
|
|
518
|
+
// Stored session inputs (qa_agent needs_input / first-run credentials) fill ${VAR}s so a
|
|
519
|
+
// generated flow replays without the agent re-sending raw secrets; explicit args win.
|
|
520
|
+
const sessionInputs = sessions.inputVariables(session);
|
|
521
|
+
const runVariables = { ...sessionInputs, ...(variables ?? {}) };
|
|
522
|
+
const usedSessionInputs = Object.keys(sessionInputs).filter((k) => variables?.[k] == null);
|
|
523
|
+
if (usedSessionInputs.length)
|
|
524
|
+
notes.push(`using stored session input(s): ${usedSessionInputs.join(', ')} (values not shown)`);
|
|
525
|
+
const affects = flowMutationAffects(parsed, session, src.source, runs, externalProviders, runVariables);
|
|
453
526
|
const mutationRisk = flowMutationRisk(affects);
|
|
454
527
|
let mutationConsent = {
|
|
455
528
|
required: false,
|
|
@@ -475,11 +548,17 @@ export function registerFlow(server, sessions) {
|
|
|
475
548
|
return qaAnnotate(requireConsent({
|
|
476
549
|
action: 'flow_mutation_run',
|
|
477
550
|
risk: mutationRisk,
|
|
478
|
-
exactCommand: `qa_flow_run ${parsed.name} (${[...affects.mutations.map((m) => `${m.step}:${m.kind}`), ...externalProviderSteps.map((s) => `${s.step}:${s.kind}`)].join(', ')})${externalCommand}`,
|
|
551
|
+
exactCommand: `qa_flow_run ${parsed.name} (${[...affects.mutations.map((m) => `${m.step}:${m.kind}`), ...externalProviderSteps.map((s) => `${s.step}:${s.kind}`)].join(', ')})${externalCommand}${mutationCommandLines(affects).length ? `\n${mutationCommandLines(affects).join('\n')}` : ''}`,
|
|
479
552
|
affects,
|
|
480
|
-
explain: affects.externalProviders.length
|
|
553
|
+
explain: (affects.externalProviders.length
|
|
481
554
|
? `Run flow "${parsed.name}" with external visual provider steps? OCR steps pass ${runs * externalProviderSteps.length} screenshot(s) to the configured provider and any mutating steps can change app/device/test state.`
|
|
482
|
-
: `Run mutating flow "${parsed.name}"? Mutating steps can change app/device/test state and will be recorded in the mutation ledger
|
|
555
|
+
: `Run mutating flow "${parsed.name}"? Mutating steps can change app/device/test state and will be recorded in the mutation ledger.`) +
|
|
556
|
+
(affects.mutations.some((m) => m.kind === 'seed')
|
|
557
|
+
? " Seed steps run the exact command/URL shown, taken from the repo's fixture declarations. These are repo-supplied and UNREVIEWED by Swipium; approve only if you trust them."
|
|
558
|
+
: '') +
|
|
559
|
+
(affects.mutations.some((m) => m.kind === 'openUrl')
|
|
560
|
+
? ' openUrl steps interpolate variables into a URL that leaves the test harness, so check the destination.'
|
|
561
|
+
: ''),
|
|
483
562
|
}), notes);
|
|
484
563
|
}
|
|
485
564
|
mutationConsent = { required: true, consentId, approved: true, payloadHash };
|
|
@@ -499,7 +578,7 @@ export function registerFlow(server, sessions) {
|
|
|
499
578
|
const b = sessions.budgetStop(session);
|
|
500
579
|
if (b)
|
|
501
580
|
break;
|
|
502
|
-
results.push(await runFlow(sessions, session, driver, parsed, { variables, mutationConsent }));
|
|
581
|
+
results.push(await runFlow(sessions, session, driver, parsed, { variables: runVariables, mutationConsent }));
|
|
503
582
|
}
|
|
504
583
|
const flake = classifyFlakeResults(results, runs);
|
|
505
584
|
return qaAnnotate(qaOk({
|
|
@@ -516,17 +595,18 @@ export function registerFlow(server, sessions) {
|
|
|
516
595
|
reason: r.reason,
|
|
517
596
|
failureCode: r.failureCode,
|
|
518
597
|
})),
|
|
519
|
-
}, `flow "${parsed.name}" × ${results.length}: ${flake.passed} passed, ${flake.failed} failed
|
|
598
|
+
}, `flow "${parsed.name}" × ${results.length}: ${flake.passed} passed, ${flake.failed} failed > ${flake.classification === 'flaky' ? '⚠ FLAKY' : flake.classification === 'deterministic-pass' ? '✅ deterministic-pass' : '❌ deterministic-fail'} (${flake.passRate}% pass-rate); triage=${flake.triage.likelyCause}`), notes);
|
|
520
599
|
}
|
|
521
|
-
const result = await runFlow(sessions, session, driver, parsed, { variables, mutationConsent });
|
|
600
|
+
const result = await runFlow(sessions, session, driver, parsed, { variables: runVariables, mutationConsent });
|
|
522
601
|
const head = `flow "${result.name}" ${result.passed ? '✅ PASSED' : `❌ FAILED at step ${result.failedAtStep} (${result.reason})`} ` +
|
|
523
|
-
|
|
602
|
+
`: ${result.steps.filter((s) => s.ok).length}/${result.steps.length} steps in ${Math.round(result.durationMs / 100) / 10}s` +
|
|
524
603
|
(result.appHealth ? `\nhealth: native=${result.nativeHealth} app=${result.appHealth}` : '');
|
|
525
604
|
const stepLines = result.steps
|
|
526
|
-
.map((s) => `${s.ok ? '✓' : '✗'} ${s.index}. ${s.summary}${s.detail ?
|
|
605
|
+
.map((s) => `${s.ok ? '✓' : '✗'} ${s.index}. ${s.summary}${s.detail ? `: ${s.detail}` : ''}${s.screenshotUri ? `\n evidence: ${s.screenshotUri}` : ''}`)
|
|
527
606
|
.join('\n');
|
|
528
|
-
// A failed flow is a structured result, not a protocol error
|
|
529
|
-
|
|
607
|
+
// A failed flow is a structured result, not a protocol error. The agent should record it.
|
|
608
|
+
const nextSteps = result.passed ? undefined : failedFlowNextSteps(parsed.name, flow, result.failedAtStep, result.failureCode);
|
|
609
|
+
return qaAnnotate(qaOk({ ...result, ...(nextSteps ? { nextSteps } : {}) }, `${head}\n${stepLines}${nextSteps ? `\nnext:\n - ${nextSteps.join('\n - ')}` : ''}`), notes);
|
|
530
610
|
});
|
|
531
611
|
}
|
|
532
612
|
//# sourceMappingURL=flow.js.map
|