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/flows/run.js
CHANGED
|
@@ -1,78 +1,83 @@
|
|
|
1
|
-
// Flow runner v2 (
|
|
1
|
+
// Flow runner v2 (Flow System V2). Drives a parsed Flow step-by-step using core
|
|
2
2
|
// primitives + the driver directly, WITHOUT re-entering through the model. Fail-fast, never
|
|
3
|
-
// auto-retries
|
|
3
|
+
// auto-retries. v2 adds: setup/teardown phases (teardown always runs), selector-bound
|
|
4
4
|
// inputText, visual steps (image match / diff / visual evidence), device-relative gestures,
|
|
5
5
|
// overlay/network/lifecycle/seed/note steps, and typed per-step failure codes. On failure it
|
|
6
6
|
// captures a screenshot + health so the result stands alone.
|
|
7
7
|
import { readFileSync, existsSync } from 'node:fs';
|
|
8
|
-
import {
|
|
8
|
+
import { join } from 'node:path';
|
|
9
9
|
import { createHash } from 'node:crypto';
|
|
10
10
|
import { parseSnapshot, signature } from '../snapshot/parse.js';
|
|
11
11
|
import { settle } from '../snapshot/settle.js';
|
|
12
12
|
import { checkHealth } from '../oracle/health.js';
|
|
13
13
|
import { recordHealthFindings } from '../oracle/record.js';
|
|
14
|
+
import { isAbortError } from '../lib/abortScope.js';
|
|
14
15
|
import { boundsBucket, resolveTarget, resourceIdMatches } from '../core/target.js';
|
|
15
16
|
import { imageDiff, findTemplate } from '../lib/image.js';
|
|
16
17
|
import { captureCoordinateSpace, toDevicePoint } from '../lib/coordSpace.js';
|
|
17
18
|
import { executeSeed } from './seedExec.js';
|
|
18
|
-
import {
|
|
19
|
+
import { withinRootOrNull } from './paths.js';
|
|
20
|
+
import { lookupFlowVar, missingVarMessage, resolveVars, SECRET_VAR_NAME, } from './schema.js';
|
|
21
|
+
// Shared with qa_act: flow replay keeps the default inset 0 so recorded flows
|
|
22
|
+
// reproduce their historical vectors byte-for-byte.
|
|
23
|
+
import { swipeVector } from '../lib/gestures.js';
|
|
19
24
|
import { configuredOcrCommand, findOcrRegion, runOcr } from '../visual/ocr.js';
|
|
20
25
|
import { makeRedactor } from '../lib/redact.js';
|
|
21
|
-
|
|
22
|
-
up: [540, 1500, 540, 600],
|
|
23
|
-
down: [540, 600, 540, 1500],
|
|
24
|
-
left: [800, 1100, 200, 1100],
|
|
25
|
-
right: [200, 1100, 800, 1100],
|
|
26
|
-
};
|
|
27
|
-
const AREA_ANCHOR = { center: [0.5, 0.5], top: [0.5, 0.3], bottom: [0.5, 0.7], left: [0.3, 0.5], right: [0.7, 0.5] };
|
|
28
|
-
const SECRET_VAR_NAME = /pass|secret|token|otp|pin|cvv|key/i;
|
|
29
|
-
/** Device-relative swipe vector (fractions of the screen), falling back to fixed coords if no size. */
|
|
30
|
-
function swipeVector(size, dir, area = 'center', distance = 0.6) {
|
|
31
|
-
if (!size)
|
|
32
|
-
return FALLBACK_SWIPE[dir];
|
|
33
|
-
const [ax, ay] = AREA_ANCHOR[area];
|
|
34
|
-
const cx = size.width * ax;
|
|
35
|
-
const cy = size.height * ay;
|
|
36
|
-
const vd = (size.height * distance) / 2;
|
|
37
|
-
const hd = (size.width * distance) / 2;
|
|
38
|
-
const clampX = (x) => Math.max(1, Math.min(size.width - 1, Math.round(x)));
|
|
39
|
-
const clampY = (y) => Math.max(1, Math.min(size.height - 1, Math.round(y)));
|
|
40
|
-
const v = {
|
|
41
|
-
up: [cx, cy + vd, cx, cy - vd],
|
|
42
|
-
down: [cx, cy - vd, cx, cy + vd],
|
|
43
|
-
left: [cx + hd, cy, cx - hd, cy],
|
|
44
|
-
right: [cx - hd, cy, cx + hd, cy],
|
|
45
|
-
}[dir];
|
|
46
|
-
return [clampX(v[0]), clampY(v[1]), clampX(v[2]), clampY(v[3])];
|
|
47
|
-
}
|
|
26
|
+
import { classifyForeground, detectTreeOverlays } from '../snapshot/overlays.js';
|
|
48
27
|
function describe(step) {
|
|
49
28
|
switch (step.kind) {
|
|
50
|
-
case 'tap':
|
|
51
|
-
|
|
52
|
-
case '
|
|
53
|
-
|
|
54
|
-
case '
|
|
55
|
-
|
|
56
|
-
case '
|
|
57
|
-
|
|
58
|
-
case '
|
|
59
|
-
|
|
60
|
-
case '
|
|
61
|
-
|
|
62
|
-
case '
|
|
63
|
-
|
|
64
|
-
case '
|
|
65
|
-
|
|
66
|
-
case '
|
|
67
|
-
|
|
68
|
-
case '
|
|
69
|
-
|
|
70
|
-
case '
|
|
71
|
-
|
|
72
|
-
case '
|
|
73
|
-
|
|
74
|
-
case '
|
|
75
|
-
|
|
29
|
+
case 'tap':
|
|
30
|
+
return `tap ${step.selector}`;
|
|
31
|
+
case 'tapAt':
|
|
32
|
+
return `tapAt ${step.x},${step.y}`;
|
|
33
|
+
case 'tapImage':
|
|
34
|
+
return `tapImage ${step.template}`;
|
|
35
|
+
case 'tapOcrText':
|
|
36
|
+
return `tapOcrText ${JSON.stringify(step.query)}`;
|
|
37
|
+
case 'inputText':
|
|
38
|
+
return `inputText${step.into ? ` into "${step.into}"` : ''} ${step.secret ? '«secret»' : JSON.stringify(step.value)}`;
|
|
39
|
+
case 'assertVisible':
|
|
40
|
+
return `assertVisible ${JSON.stringify(step.query)}`;
|
|
41
|
+
case 'assertNotVisible':
|
|
42
|
+
return `assertNotVisible ${JSON.stringify(step.query)}`;
|
|
43
|
+
case 'assertImage':
|
|
44
|
+
return `assertImage ${step.template}`;
|
|
45
|
+
case 'assertOcrText':
|
|
46
|
+
return `assertOcrText ${JSON.stringify(step.query)}`;
|
|
47
|
+
case 'assertVisual':
|
|
48
|
+
return `assertVisual ${JSON.stringify(step.description)}`;
|
|
49
|
+
case 'assertDiff':
|
|
50
|
+
return `assertDiff ${step.baseline}`;
|
|
51
|
+
case 'swipe':
|
|
52
|
+
return `swipe ${step.direction}${step.area ? `@${step.area}` : ''}`;
|
|
53
|
+
case 'scrollTo':
|
|
54
|
+
return `scrollTo ${JSON.stringify(step.query)}`;
|
|
55
|
+
case 'press':
|
|
56
|
+
return `press ${step.key}`;
|
|
57
|
+
case 'openUrl':
|
|
58
|
+
return `openUrl ${step.url}`;
|
|
59
|
+
case 'wait':
|
|
60
|
+
return step.ms != null ? `wait ${step.ms}ms` : `wait for ${JSON.stringify(step.query)}`;
|
|
61
|
+
case 'waitForIdle':
|
|
62
|
+
return 'waitForIdle';
|
|
63
|
+
case 'waitForVisible':
|
|
64
|
+
return `waitForVisible ${JSON.stringify(step.query)}`;
|
|
65
|
+
case 'clearOverlay':
|
|
66
|
+
return 'clearOverlay';
|
|
67
|
+
case 'networkOffline':
|
|
68
|
+
return 'networkOffline';
|
|
69
|
+
case 'networkOnline':
|
|
70
|
+
return 'networkOnline';
|
|
71
|
+
case 'restartApp':
|
|
72
|
+
return 'restartApp';
|
|
73
|
+
case 'seed':
|
|
74
|
+
return `seed ${step.fixture}`;
|
|
75
|
+
case 'note':
|
|
76
|
+
return `note(${step.outcome}) ${JSON.stringify(step.reason)}`;
|
|
77
|
+
case 'screenshot':
|
|
78
|
+
return 'screenshot';
|
|
79
|
+
case 'prepareTarget':
|
|
80
|
+
return 'prepareTarget';
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
async function snapshot(session, d) {
|
|
@@ -95,14 +100,16 @@ function resourceIdSelector(selector) {
|
|
|
95
100
|
}
|
|
96
101
|
function provenanceForStep(flow, stepIndex, kind) {
|
|
97
102
|
const oneBased = stepIndex + 1;
|
|
98
|
-
return (flow.provenance ?? []).find((entry) =>
|
|
99
|
-
(!entry.generatedKind || entry.generatedKind === kind || (kind === 'wait' && entry.generatedKind === 'waitForVisible')))
|
|
103
|
+
return (flow.provenance ?? []).find((entry) => entry.generatedStepIndex === oneBased &&
|
|
104
|
+
(!entry.generatedKind || entry.generatedKind === kind || (kind === 'wait' && entry.generatedKind === 'waitForVisible')));
|
|
100
105
|
}
|
|
101
106
|
function targetHintsFromProvenance(provenance) {
|
|
102
107
|
if (!provenance)
|
|
103
108
|
return {};
|
|
104
109
|
return {
|
|
105
|
-
packageName: provenance.resourceId?.includes(':id/')
|
|
110
|
+
packageName: provenance.resourceId?.includes(':id/')
|
|
111
|
+
? provenance.resourceId.slice(0, provenance.resourceId.indexOf(':id/'))
|
|
112
|
+
: undefined,
|
|
106
113
|
className: provenance.className ?? provenance.elementRole,
|
|
107
114
|
textHint: provenance.text ?? provenance.accessibilityLabel,
|
|
108
115
|
boundsHint: provenance.boundsBucket,
|
|
@@ -178,17 +185,57 @@ async function resourceIdVisibility(session, d, id, provenance) {
|
|
|
178
185
|
const matches = parsed.allNodes.filter((node) => resourceIdMatches(undefined, node.id, id));
|
|
179
186
|
const screenMismatch = provenance?.originalScreenSignature && flowScreenSignature(parsed) !== provenance.originalScreenSignature;
|
|
180
187
|
if (!matches.length)
|
|
181
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
visible: false,
|
|
190
|
+
detail: `resource-id "${id}" not visible${screenMismatch ? ' (recorded screen signature differs from current screen)' : ''}`,
|
|
191
|
+
};
|
|
182
192
|
const narrowed = matches.length > 1 ? narrowRawResourceMatches(matches, provenance) : matches;
|
|
183
193
|
if (narrowed.length > 1) {
|
|
184
194
|
const labels = narrowed.map((node, index) => `${index + 1}:${node.cls}:${node.text || node.desc || node.id || 'unlabeled'}`).join(', ');
|
|
185
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
visible: true,
|
|
197
|
+
ambiguous: true,
|
|
198
|
+
detail: `AMBIGUOUS_SELECTOR: resource-id ${id} matched ${narrowed.length} visible nodes (${labels}); add text/class/bounds hints or a unique testID${screenMismatch ? ' (recorded screen signature differs from current screen)' : ''}.`,
|
|
199
|
+
};
|
|
186
200
|
}
|
|
187
201
|
const node = narrowed[0];
|
|
188
202
|
return { visible: node.enabled !== false && node.bounds[2] > node.bounds[0] && node.bounds[3] > node.bounds[1] };
|
|
189
203
|
}
|
|
204
|
+
/** In-tree modal surfaces that BACK dismisses (Android dialogs / bottom sheets / popups, iOS
|
|
205
|
+
* alerts and action sheets, system permission dialogs rendered in-tree). */
|
|
206
|
+
const MODAL_CLASS_RE = /BottomSheet|AlertDialog|DialogTitle|PopupWindow|XCUIElementTypeAlert|XCUIElementTypeSheet/;
|
|
207
|
+
const MODAL_ID_RE = /design_bottom_sheet|bottom_sheet|touch_outside|android:id\/(?:alertTitle|button1|button2|parentPanel|select_dialog_listview)|permissioncontroller:id\/|packageinstaller:id\/permission|:id\/(?:modal|dialog)[_a-z]*/i;
|
|
208
|
+
/** Classify what (if anything) the clearOverlay flow step may safely dismiss with BACK.
|
|
209
|
+
* Snackbars / banners are NOT BACK-dismissible (BACK would navigate the app instead). */
|
|
210
|
+
export function detectBackDismissibleOverlay(nodes, screen, appId, foregroundOwner) {
|
|
211
|
+
const fg = classifyForeground(appId, foregroundOwner);
|
|
212
|
+
if (fg && (fg.type === 'permission_dialog' || fg.type === 'account_picker'))
|
|
213
|
+
return fg.type;
|
|
214
|
+
const tree = detectTreeOverlays(nodes, screen).find((o) => o.type === 'native_dialog' || o.type === 'rn_logbox' || o.type === 'rn_redbox');
|
|
215
|
+
if (tree)
|
|
216
|
+
return tree.type;
|
|
217
|
+
const modal = nodes.find((n) => MODAL_CLASS_RE.test(n.cls) || MODAL_ID_RE.test(n.id));
|
|
218
|
+
if (modal)
|
|
219
|
+
return /permission/i.test(modal.id) ? 'permission_dialog' : /sheet/i.test(`${modal.cls} ${modal.id}`) ? 'bottom_sheet' : 'modal';
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
190
222
|
export function classifyFlowDriverError(error, fallback = 'UNKNOWN') {
|
|
223
|
+
// Cancelled work (the call/job was aborted) is CANCELLED, never WDA_UNREACHABLE / SNAPSHOT_FAILED
|
|
224
|
+
// because the aborted request's message says "aborted" or "dump failed".
|
|
225
|
+
if (isAbortError(error))
|
|
226
|
+
return 'CANCELLED';
|
|
191
227
|
const msg = String(error?.message ?? error);
|
|
228
|
+
// A crashing OCR/mask provider (tapOcrText/assertOcrText) is typed, not UNKNOWN.
|
|
229
|
+
if (error?.code === 'OCR_PROVIDER_FAILED')
|
|
230
|
+
return 'OCR_PROVIDER_FAILED';
|
|
231
|
+
// Driver-side argument validation (e.g. a malformed app id from assertAndroidAppId) carries
|
|
232
|
+
// code INVALID_ARGUMENT and/or an `INVALID_ARGUMENT:` message prefix. That's a caller error, not UNKNOWN.
|
|
233
|
+
if (error?.code === 'INVALID_ARGUMENT' || /^(?:Error:\s*)?INVALID_ARGUMENT\b/.test(msg))
|
|
234
|
+
return 'INVALID_ARGUMENT';
|
|
235
|
+
// Drivers self-classify undeliverable text (DirectDriver/WdaDriver prefix the code), so match
|
|
236
|
+
// first so the free-form detail after the prefix can't hit a broader pattern below.
|
|
237
|
+
if (/TEXT_INPUT_UNSUPPORTED/.test(msg))
|
|
238
|
+
return 'TEXT_INPUT_UNSUPPORTED';
|
|
192
239
|
if (/not hittable|not hit.?point|not visible.*hittable|is not enabled|element.*obscured|other element.*would receive/i.test(msg))
|
|
193
240
|
return 'ELEMENT_NOT_HITTABLE';
|
|
194
241
|
if (/stale element|stale.*reference|element.*no longer|invalid element/i.test(msg))
|
|
@@ -231,22 +278,34 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
231
278
|
const steps = [];
|
|
232
279
|
const appId = flow.appId ?? session.appId;
|
|
233
280
|
const timeoutMs = 8000;
|
|
234
|
-
// Ensure session.driver is the driver we're running with
|
|
281
|
+
// Ensure session.driver is the driver we're running with. Core helpers (resolveTarget) read it.
|
|
235
282
|
session.driver = d;
|
|
236
|
-
|
|
283
|
+
// Re-read per gesture: a rotation mid-flow swaps the axes (the WDA driver caches per
|
|
284
|
+
// orientation, so this is cheap there). Keeps the first read as the fallback.
|
|
285
|
+
const initialScreenSize = await d.screenSize().catch(() => null);
|
|
286
|
+
const currentScreenSize = async () => (await d.screenSize().catch(() => null)) ?? initialScreenSize;
|
|
237
287
|
const mutationConsent = opts.mutationConsent ?? { required: false, approved: true };
|
|
238
288
|
const resolveFlowText = (value) => {
|
|
239
289
|
const resolved = resolveVars(value, vars);
|
|
240
290
|
for (const m of value.matchAll(/\$\{([^}]+)\}/g)) {
|
|
241
291
|
const name = m[1];
|
|
242
|
-
const v =
|
|
243
|
-
if (v != null && SECRET_VAR_NAME.test(name))
|
|
292
|
+
const v = lookupFlowVar(name, vars);
|
|
293
|
+
if (v != null && v !== '' && SECRET_VAR_NAME.test(name))
|
|
244
294
|
session.secrets.add(v);
|
|
245
295
|
}
|
|
246
296
|
return resolved;
|
|
247
297
|
};
|
|
248
|
-
const unresolved = (missing) => ({
|
|
298
|
+
const unresolved = (missing) => ({
|
|
299
|
+
ok: false,
|
|
300
|
+
detail: missingVarMessage(missing),
|
|
301
|
+
failureCode: 'MISSING_FIXTURE',
|
|
302
|
+
});
|
|
249
303
|
const shown = (value) => makeRedactor(session.secrets)(value) ?? value;
|
|
304
|
+
const outsideRoot = (p) => ({
|
|
305
|
+
ok: false,
|
|
306
|
+
detail: `path "${p}" resolves outside the project root. Image templates and baselines must live under the project root`,
|
|
307
|
+
failureCode: 'UNSAFE_ACTION_REFUSED',
|
|
308
|
+
});
|
|
250
309
|
const recordFlowMutation = (action, risk, target, status, detail) => {
|
|
251
310
|
sessions.recordMutation(session, {
|
|
252
311
|
tool: 'qa_flow_run',
|
|
@@ -263,8 +322,8 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
263
322
|
let reason;
|
|
264
323
|
let failureCode;
|
|
265
324
|
const tapImageHit = async (template, minScore) => {
|
|
266
|
-
const tplPath =
|
|
267
|
-
if (!existsSync(tplPath))
|
|
325
|
+
const tplPath = withinRootOrNull(session.root, template);
|
|
326
|
+
if (!tplPath || !existsSync(tplPath))
|
|
268
327
|
return null;
|
|
269
328
|
const png = await d.screenshot();
|
|
270
329
|
const m = findTemplate(png, readFileSync(tplPath), minScore ?? 0.85);
|
|
@@ -315,7 +374,11 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
315
374
|
return { ok: true };
|
|
316
375
|
}
|
|
317
376
|
if (native && native.using !== 'accessibility id' && native.using !== 'name') {
|
|
318
|
-
return {
|
|
377
|
+
return {
|
|
378
|
+
ok: false,
|
|
379
|
+
detail: `${native.using} selectors require backend-native selector support`,
|
|
380
|
+
failureCode: 'BACKEND_UNSUPPORTED',
|
|
381
|
+
};
|
|
319
382
|
}
|
|
320
383
|
const t = await resolveTarget(session, targetForTapSelector(selector, provenance));
|
|
321
384
|
if ('error' in t)
|
|
@@ -331,6 +394,8 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
331
394
|
await settle(d, { timeoutMs });
|
|
332
395
|
return { ok: true };
|
|
333
396
|
case 'tapImage': {
|
|
397
|
+
if (!withinRootOrNull(session.root, step.template))
|
|
398
|
+
return outsideRoot(step.template);
|
|
334
399
|
const pt = await tapImageHit(step.template, step.minScore);
|
|
335
400
|
if (!pt)
|
|
336
401
|
return { ok: false, detail: `image "${step.template}" not found on screen`, failureCode: 'ELEMENT_NOT_FOUND' };
|
|
@@ -345,7 +410,11 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
345
410
|
return unresolved(resolved.missing);
|
|
346
411
|
const query = resolved.out;
|
|
347
412
|
if (session.sensitive)
|
|
348
|
-
return {
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
detail: 'Sensitive mode refuses OCR visual text search because it would pass a screenshot to an external provider',
|
|
416
|
+
failureCode: 'UNSAFE_ACTION_REFUSED',
|
|
417
|
+
};
|
|
349
418
|
const command = configuredOcrCommand(session.root);
|
|
350
419
|
if (!command)
|
|
351
420
|
return { ok: false, detail: 'OCR command is not configured', failureCode: 'VISUAL_ONLY_SCREEN' };
|
|
@@ -360,9 +429,17 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
360
429
|
case 'inputText': {
|
|
361
430
|
const { out, missing } = resolveFlowText(step.value);
|
|
362
431
|
if (missing.length)
|
|
363
|
-
return
|
|
432
|
+
return unresolved(missing);
|
|
433
|
+
// Register a secret BEFORE typing so a failing driver call's error is scrubbed too (H2).
|
|
434
|
+
if (step.secret && out)
|
|
435
|
+
session.secrets.add(out);
|
|
436
|
+
// eslint-disable-next-line no-control-regex -- intentional: detect non-ASCII (outside \x00-\x7F) before adb text input
|
|
364
437
|
if (d.kind === 'direct' && /[^\x00-\x7F]/.test(out)) {
|
|
365
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
ok: false,
|
|
440
|
+
detail: 'Android DirectDriver text entry is ASCII-safe only for generated replay values',
|
|
441
|
+
failureCode: 'TEXT_INPUT_UNSUPPORTED',
|
|
442
|
+
};
|
|
366
443
|
}
|
|
367
444
|
if (step.into) {
|
|
368
445
|
const into = resolveFlowText(step.into);
|
|
@@ -384,12 +461,20 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
384
461
|
return { ok: true };
|
|
385
462
|
}
|
|
386
463
|
if (native && native.using !== 'accessibility id' && native.using !== 'name') {
|
|
387
|
-
return {
|
|
464
|
+
return {
|
|
465
|
+
ok: false,
|
|
466
|
+
detail: `${native.using} selectors require backend-native selector support`,
|
|
467
|
+
failureCode: 'BACKEND_UNSUPPORTED',
|
|
468
|
+
};
|
|
388
469
|
}
|
|
389
470
|
// selector-bound: focus the named field first (no reliance on existing focus)
|
|
390
471
|
const t = await resolveTarget(session, targetForTapSelector(into.out, provenance));
|
|
391
472
|
if ('error' in t)
|
|
392
|
-
return {
|
|
473
|
+
return {
|
|
474
|
+
ok: false,
|
|
475
|
+
detail: `field "${shown(into.out)}": ${t.error}`,
|
|
476
|
+
failureCode: classifyFlowDriverError(t.error, 'ELEMENT_NOT_FOUND'),
|
|
477
|
+
};
|
|
393
478
|
await d.tapXY(t.x, t.y);
|
|
394
479
|
await new Promise((r) => setTimeout(r, 600));
|
|
395
480
|
await d.clearFocusedText(t.textLen);
|
|
@@ -418,7 +503,9 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
418
503
|
const visibleById = await resourceIdVisibility(session, d, id, provenance);
|
|
419
504
|
if (visibleById.ambiguous)
|
|
420
505
|
return { ok: false, detail: visibleById.detail, failureCode: 'AMBIGUOUS_SELECTOR' };
|
|
421
|
-
return visibleById.visible
|
|
506
|
+
return visibleById.visible
|
|
507
|
+
? { ok: true }
|
|
508
|
+
: { ok: false, detail: visibleById.detail ?? `"${shown(query)}" not visible`, failureCode: 'ASSERTION_FAILED' };
|
|
422
509
|
}
|
|
423
510
|
const ok = visible(await snapshot(session, d), fallbackVisibleQuery(query));
|
|
424
511
|
return ok ? { ok } : { ok, detail: `"${shown(query)}" not visible`, failureCode: 'ASSERTION_FAILED' };
|
|
@@ -431,7 +518,9 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
431
518
|
try {
|
|
432
519
|
const native = await nativeVisible(d, query);
|
|
433
520
|
if (native != null)
|
|
434
|
-
return !native
|
|
521
|
+
return !native
|
|
522
|
+
? { ok: true }
|
|
523
|
+
: { ok: false, detail: `"${shown(query)}" unexpectedly visible`, failureCode: 'ASSERTION_FAILED' };
|
|
435
524
|
}
|
|
436
525
|
catch (e) {
|
|
437
526
|
return { ok: false, detail: String(e.message ?? e), failureCode: 'BACKEND_UNSUPPORTED' };
|
|
@@ -441,17 +530,23 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
441
530
|
const visibleById = await resourceIdVisibility(session, d, id, provenance);
|
|
442
531
|
if (visibleById.ambiguous)
|
|
443
532
|
return { ok: false, detail: visibleById.detail, failureCode: 'AMBIGUOUS_SELECTOR' };
|
|
444
|
-
return !visibleById.visible
|
|
533
|
+
return !visibleById.visible
|
|
534
|
+
? { ok: true }
|
|
535
|
+
: { ok: false, detail: `"${shown(query)}" unexpectedly visible`, failureCode: 'ASSERTION_FAILED' };
|
|
445
536
|
}
|
|
446
537
|
const ok = !visible(await snapshot(session, d), fallbackVisibleQuery(query));
|
|
447
538
|
return ok ? { ok } : { ok, detail: `"${shown(query)}" unexpectedly visible`, failureCode: 'ASSERTION_FAILED' };
|
|
448
539
|
}
|
|
449
540
|
case 'assertImage': {
|
|
450
|
-
const tplPath =
|
|
541
|
+
const tplPath = withinRootOrNull(session.root, step.template);
|
|
542
|
+
if (!tplPath)
|
|
543
|
+
return outsideRoot(step.template);
|
|
451
544
|
if (!existsSync(tplPath))
|
|
452
545
|
return { ok: false, detail: `template not found: ${tplPath}`, failureCode: 'ASSERTION_FAILED' };
|
|
453
546
|
const m = findTemplate(await d.screenshot(), readFileSync(tplPath), step.minScore ?? 0.85);
|
|
454
|
-
return m.found
|
|
547
|
+
return m.found
|
|
548
|
+
? { ok: true }
|
|
549
|
+
: { ok: false, detail: `image "${step.template}" not visible (best score ${m.score})`, failureCode: 'ASSERTION_FAILED' };
|
|
455
550
|
}
|
|
456
551
|
case 'assertOcrText': {
|
|
457
552
|
const resolved = resolveFlowText(step.query);
|
|
@@ -459,7 +554,11 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
459
554
|
return unresolved(resolved.missing);
|
|
460
555
|
const query = resolved.out;
|
|
461
556
|
if (session.sensitive)
|
|
462
|
-
return {
|
|
557
|
+
return {
|
|
558
|
+
ok: false,
|
|
559
|
+
detail: 'Sensitive mode refuses OCR visual text assertion because it would pass a screenshot to an external provider',
|
|
560
|
+
failureCode: 'UNSAFE_ACTION_REFUSED',
|
|
561
|
+
};
|
|
463
562
|
const command = configuredOcrCommand(session.root);
|
|
464
563
|
if (!command)
|
|
465
564
|
return { ok: false, detail: 'OCR command is not configured', failureCode: 'VISUAL_ONLY_SCREEN' };
|
|
@@ -468,22 +567,51 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
468
567
|
}
|
|
469
568
|
case 'assertVisual': {
|
|
470
569
|
// human-readable visual checkpoint: capture evidence + record a visual note; passes.
|
|
570
|
+
// Sensitive mode never persists screenshots, so record an honest skipped checkpoint instead.
|
|
571
|
+
if (session.sensitive) {
|
|
572
|
+
sessions.addNote(session, {
|
|
573
|
+
at: Date.now(),
|
|
574
|
+
workflow: `${flow.name}:visual`,
|
|
575
|
+
outcome: 'skipped',
|
|
576
|
+
reason: `${step.description}: not captured, sensitive mode disables screenshots`,
|
|
577
|
+
method: 'visual',
|
|
578
|
+
});
|
|
579
|
+
return { ok: true, detail: 'sensitive mode: visual checkpoint screenshot not captured' };
|
|
580
|
+
}
|
|
471
581
|
const png = await d.screenshot();
|
|
472
582
|
const uri = sessions.saveArtifact(session, 'screenshot', `flow-visual-${Date.now()}.png`, png, 'image/png', `visual checkpoint: ${step.description}`);
|
|
473
583
|
sessions.bump(session, 'screenshots');
|
|
474
|
-
sessions.addNote(session, {
|
|
584
|
+
sessions.addNote(session, {
|
|
585
|
+
at: Date.now(),
|
|
586
|
+
workflow: `${flow.name}:visual`,
|
|
587
|
+
outcome: 'pass',
|
|
588
|
+
reason: step.description,
|
|
589
|
+
method: 'visual',
|
|
590
|
+
verifiedVisually: true,
|
|
591
|
+
artifactUris: [uri],
|
|
592
|
+
});
|
|
475
593
|
return { ok: true };
|
|
476
594
|
}
|
|
477
595
|
case 'assertDiff': {
|
|
478
|
-
const basePath = join(session.root, '.swipium', 'baselines', `${step.baseline}.png`);
|
|
596
|
+
const basePath = withinRootOrNull(join(session.root, '.swipium', 'baselines'), `${step.baseline}.png`);
|
|
597
|
+
if (!basePath)
|
|
598
|
+
return outsideRoot(step.baseline);
|
|
479
599
|
if (!existsSync(basePath))
|
|
480
600
|
return { ok: false, detail: `no baseline "${step.baseline}" (create a visual baseline first)`, failureCode: 'MISSING_FIXTURE' };
|
|
481
601
|
const res = imageDiff(readFileSync(basePath), await d.screenshot());
|
|
482
602
|
const tol = step.threshold ?? 0.02;
|
|
483
|
-
return res.comparable && res.ratio <= tol
|
|
603
|
+
return res.comparable && res.ratio <= tol
|
|
604
|
+
? { ok: true }
|
|
605
|
+
: {
|
|
606
|
+
ok: false,
|
|
607
|
+
detail: res.comparable
|
|
608
|
+
? `${(res.ratio * 100).toFixed(2)}% changed > ${(tol * 100).toFixed(1)}%`
|
|
609
|
+
: `not comparable: ${res.reason}`,
|
|
610
|
+
failureCode: 'ASSERTION_FAILED',
|
|
611
|
+
};
|
|
484
612
|
}
|
|
485
613
|
case 'swipe': {
|
|
486
|
-
const v = swipeVector(
|
|
614
|
+
const v = swipeVector(await currentScreenSize(), step.direction, step.area, step.distance);
|
|
487
615
|
await d.swipe(v[0], v[1], v[2], v[3], 300);
|
|
488
616
|
sessions.bump(session, 'actions');
|
|
489
617
|
await settle(d, { timeoutMs });
|
|
@@ -494,6 +622,7 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
494
622
|
if (resolved.missing.length)
|
|
495
623
|
return unresolved(resolved.missing);
|
|
496
624
|
const query = resolved.out;
|
|
625
|
+
const screenSize = await currentScreenSize();
|
|
497
626
|
for (let n = 0; n < 8; n++) {
|
|
498
627
|
try {
|
|
499
628
|
const native = await nativeVisible(d, query);
|
|
@@ -534,7 +663,7 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
534
663
|
case 'openUrl': {
|
|
535
664
|
const { out, missing } = resolveFlowText(step.url);
|
|
536
665
|
if (missing.length)
|
|
537
|
-
return
|
|
666
|
+
return unresolved(missing);
|
|
538
667
|
await d.openUrl(out);
|
|
539
668
|
await settle(d, { timeoutMs });
|
|
540
669
|
return { ok: true };
|
|
@@ -553,7 +682,7 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
553
682
|
if (resolved.missing.length)
|
|
554
683
|
return unresolved(resolved.missing);
|
|
555
684
|
const query = resolved.out;
|
|
556
|
-
const deadline = Date.now() + (step.kind === 'waitForVisible' ? step.timeoutMs ?? timeoutMs : timeoutMs);
|
|
685
|
+
const deadline = Date.now() + (step.kind === 'waitForVisible' ? (step.timeoutMs ?? timeoutMs) : timeoutMs);
|
|
557
686
|
const waitStarted = Date.now();
|
|
558
687
|
while (Date.now() < deadline) {
|
|
559
688
|
try {
|
|
@@ -602,13 +731,62 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
602
731
|
return { ok: true };
|
|
603
732
|
}
|
|
604
733
|
case 'clearOverlay': {
|
|
605
|
-
//
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
734
|
+
// Best-effort cleanup (always ok), but BACK only when something is there to dismiss:
|
|
735
|
+
// with nothing open, BACK navigates away / exits the app (H7).
|
|
736
|
+
if (await d.imeShown().catch(() => false)) {
|
|
737
|
+
if (d.hideKeyboard)
|
|
738
|
+
await d.hideKeyboard().catch(() => false);
|
|
739
|
+
else
|
|
740
|
+
await d.pressKey('back');
|
|
741
|
+
await settle(d, { timeoutMs });
|
|
742
|
+
return { ok: true, detail: 'keyboard hidden' };
|
|
743
|
+
}
|
|
744
|
+
const parsed = await snapshot(session, d).catch(() => null);
|
|
745
|
+
const fg = await d.foregroundOwner().catch(() => 'unknown');
|
|
746
|
+
const dismissible = detectBackDismissibleOverlay(parsed?.allNodes ?? [], parsed?.screen, session.appId, fg);
|
|
747
|
+
if (!dismissible) {
|
|
748
|
+
return {
|
|
749
|
+
ok: true,
|
|
750
|
+
nothingCleared: true,
|
|
751
|
+
detail: 'nothing to clear (no keyboard, dialog, sheet or permission prompt detected), BACK not pressed',
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
// iOS has no BACK for alerts/sheets: WDA "back" taps the nav bar / edge-swipes, which
|
|
755
|
+
// navigates the app instead of dismissing the alert. Use the native alert API, and never
|
|
756
|
+
// claim success when it is unavailable or the overlay is still there.
|
|
757
|
+
if (d.kind === 'wda' || d.kind === 'simulator') {
|
|
758
|
+
if (!d.dismissAlert) {
|
|
759
|
+
return {
|
|
760
|
+
ok: true,
|
|
761
|
+
nothingCleared: false,
|
|
762
|
+
detail: `${dismissible} detected but NOT dismissed. This iOS backend has no native alert API (BACK is not used on iOS); dismiss it with an explicit tap step`,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
await d.dismissAlert();
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
return {
|
|
770
|
+
ok: true,
|
|
771
|
+
nothingCleared: false,
|
|
772
|
+
detail: `${dismissible} detected but NOT dismissed: native alert dismiss failed: ${String(e?.message ?? e)}`,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
await settle(d, { timeoutMs });
|
|
776
|
+
const after = await snapshot(session, d).catch(() => null);
|
|
777
|
+
const fgAfter = await d.foregroundOwner().catch(() => 'unknown');
|
|
778
|
+
const still = after ? detectBackDismissibleOverlay(after.allNodes, after.screen, session.appId, fgAfter) : undefined;
|
|
779
|
+
if (still)
|
|
780
|
+
return {
|
|
781
|
+
ok: true,
|
|
782
|
+
nothingCleared: false,
|
|
783
|
+
detail: `${dismissible} still present after native alert dismiss. Dismiss it with an explicit tap step`,
|
|
784
|
+
};
|
|
785
|
+
return { ok: true, detail: `dismissed ${dismissible} with the native alert API` };
|
|
786
|
+
}
|
|
787
|
+
await d.pressKey('back');
|
|
610
788
|
await settle(d, { timeoutMs });
|
|
611
|
-
return { ok: true };
|
|
789
|
+
return { ok: true, detail: `dismissed ${dismissible} with BACK` };
|
|
612
790
|
}
|
|
613
791
|
case 'networkOffline':
|
|
614
792
|
case 'networkOnline': {
|
|
@@ -637,7 +815,15 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
637
815
|
const r = await executeSeed(sessions, session, d, step.fixture, fixture.seed);
|
|
638
816
|
const risk = fixture.seed.type === 'script' ? 'high' : fixture.seed.type === 'api' ? 'medium' : 'low';
|
|
639
817
|
if (!r.ok) {
|
|
640
|
-
sessions.addNote(session, {
|
|
818
|
+
sessions.addNote(session, {
|
|
819
|
+
at: Date.now(),
|
|
820
|
+
workflow: `seed:${step.fixture}`,
|
|
821
|
+
outcome: 'blocked',
|
|
822
|
+
category: 'missing_test_data',
|
|
823
|
+
reason: `seed failed: ${r.detail}`,
|
|
824
|
+
requiredState: fixture.requiredState,
|
|
825
|
+
recommendedSetup: fixture.recommendedSetup,
|
|
826
|
+
});
|
|
641
827
|
recordFlowMutation('flow_seed_state', risk, { stepKind: step.kind, fixture: step.fixture, seedType: fixture.seed.type }, 'blocked', r.detail);
|
|
642
828
|
return { ok: false, detail: `seed "${step.fixture}" failed: ${r.detail}`, failureCode: 'SEED_FAILED' };
|
|
643
829
|
}
|
|
@@ -648,6 +834,8 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
648
834
|
sessions.addNote(session, { at: Date.now(), workflow: `${flow.name}:note`, outcome: step.outcome, reason: step.reason });
|
|
649
835
|
return { ok: true };
|
|
650
836
|
case 'screenshot': {
|
|
837
|
+
if (session.sensitive)
|
|
838
|
+
return { ok: true, detail: 'sensitive mode: screenshot not captured' };
|
|
651
839
|
const png = await d.screenshot();
|
|
652
840
|
sessions.saveArtifact(session, 'screenshot', `flow-shot-${Date.now()}.png`, png, 'image/png', step.reason ?? 'flow screenshot');
|
|
653
841
|
sessions.bump(session, 'screenshots');
|
|
@@ -670,7 +858,7 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
670
858
|
reason,
|
|
671
859
|
failureCode,
|
|
672
860
|
steps,
|
|
673
|
-
|
|
861
|
+
durationMs: Math.round(elapsedMs),
|
|
674
862
|
counters: session.counters,
|
|
675
863
|
};
|
|
676
864
|
};
|
|
@@ -682,7 +870,7 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
682
870
|
const summary = describe(step);
|
|
683
871
|
const budget = sessions.budgetStop(session);
|
|
684
872
|
if (budget && phase !== 'teardown') {
|
|
685
|
-
steps.push({ index, phase, kind: step.kind, summary, ok: false,
|
|
873
|
+
steps.push({ index, phase, kind: step.kind, summary, ok: false, durationMs: 0, detail: budget });
|
|
686
874
|
passed = false;
|
|
687
875
|
failedAtStep = index;
|
|
688
876
|
reason = `budget: ${budget}`;
|
|
@@ -696,24 +884,45 @@ export async function runFlow(sessions, session, d, flow, opts = {}) {
|
|
|
696
884
|
catch (e) {
|
|
697
885
|
outcome = { ok: false, detail: String(e), failureCode: classifyFlowDriverError(e) };
|
|
698
886
|
}
|
|
699
|
-
|
|
887
|
+
// H2: step detail can carry driver errors (argv/stderr), so scrub session secrets, which
|
|
888
|
+
// include every value typed into a secret step/variable before it ran.
|
|
889
|
+
if (outcome.detail)
|
|
890
|
+
outcome = { ...outcome, detail: shown(outcome.detail) };
|
|
891
|
+
const rec = {
|
|
892
|
+
index,
|
|
893
|
+
phase,
|
|
894
|
+
kind: step.kind,
|
|
895
|
+
summary: shown(summary),
|
|
896
|
+
ok: outcome.ok,
|
|
897
|
+
durationMs: Math.round(Date.now() - stepStarted),
|
|
898
|
+
detail: outcome.detail,
|
|
899
|
+
failureCode: outcome.failureCode,
|
|
900
|
+
...(outcome.nothingCleared !== undefined ? { nothingCleared: outcome.nothingCleared } : {}),
|
|
901
|
+
};
|
|
700
902
|
steps.push(rec);
|
|
701
903
|
if (!outcome.ok && failFast) {
|
|
702
904
|
passed = false;
|
|
703
905
|
failedAtStep = index;
|
|
704
906
|
reason = outcome.detail ?? 'step failed';
|
|
705
907
|
failureCode = outcome.failureCode;
|
|
908
|
+
// Sensitive mode never persists screenshots, including failure evidence.
|
|
909
|
+
if (session.sensitive) {
|
|
910
|
+
rec.detail = `${rec.detail ?? 'step failed'} (sensitive mode: failure screenshot not captured)`;
|
|
911
|
+
return index;
|
|
912
|
+
}
|
|
706
913
|
try {
|
|
707
914
|
const png = await d.screenshot();
|
|
708
915
|
rec.screenshotUri = sessions.saveArtifact(session, 'screenshot', `flow-fail-${index}-${Date.now()}.png`, png, 'image/png', `flow ${flow.name} failed at step ${index}: ${summary}`);
|
|
709
916
|
}
|
|
710
|
-
catch {
|
|
917
|
+
catch {
|
|
918
|
+
/* best-effort */
|
|
919
|
+
}
|
|
711
920
|
return index;
|
|
712
921
|
}
|
|
713
922
|
}
|
|
714
923
|
return -1;
|
|
715
924
|
};
|
|
716
|
-
// setup
|
|
925
|
+
// setup > main > teardown (teardown ALWAYS runs, even after a failure).
|
|
717
926
|
let nextBase = 0;
|
|
718
927
|
const setupFail = await runPhase(flow.setup, 'setup', nextBase, true);
|
|
719
928
|
nextBase += flow.setup.length;
|