swipium 1.5.0 → 2.0.1
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 +183 -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/explore/runner.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Guided exploration runner (Phase 3.3 Milestones D/E/G). Bounded, safe-by-default screen crawl:
|
|
2
|
-
// observe
|
|
2
|
+
// observe > signature > rank safe candidates > act > health-check > record edge, until a budget is
|
|
3
3
|
// reached. Visual-only screens (poor UI tree) are verified with a screenshot + visual assertion
|
|
4
4
|
// instead of blind coordinate crawling. Honest: unsafe actions are skipped with a note; an auth
|
|
5
|
-
// wall with no credentials returns NeedsInput. Drives only the Driver interface
|
|
5
|
+
// wall with no credentials returns NeedsInput. Drives only the Driver interface, no MCP-over-MCP.
|
|
6
6
|
import { parseSnapshot } from '../snapshot/parse.js';
|
|
7
7
|
import { settle } from '../snapshot/settle.js';
|
|
8
8
|
import { checkHealth } from '../oracle/health.js';
|
|
9
9
|
import { recordHealthFindings } from '../oracle/record.js';
|
|
10
|
+
import { CancelledError, isAbortError } from '../lib/abortScope.js';
|
|
10
11
|
import { ExploreGraph } from './graph.js';
|
|
11
12
|
import { structuredSignature, visualSignature } from './signatures.js';
|
|
12
13
|
import { actionLikeNonInteractive, rankCandidates, locatorQuality } from './candidates.js';
|
|
@@ -55,7 +56,7 @@ function valueForField(session, label, locatorValue, role, secure, gen) {
|
|
|
55
56
|
const resolved = resolveFixtureValue(session, label, locatorValue, { role });
|
|
56
57
|
if (resolved)
|
|
57
58
|
return resolved;
|
|
58
|
-
// Built-in safe generator
|
|
59
|
+
// Built-in safe generator, only when generation is explicitly allowed for this environment.
|
|
59
60
|
if (gen?.allowed) {
|
|
60
61
|
const field = fieldKindFromHints(label, locatorValue, undefined, secure);
|
|
61
62
|
const g = generateFieldValue(field, gen.policy);
|
|
@@ -83,7 +84,7 @@ function strategyToKind(strategy) {
|
|
|
83
84
|
case 'accessibility':
|
|
84
85
|
return 'accessibility_id';
|
|
85
86
|
case 'id':
|
|
86
|
-
return 'resource_id'; // Android resource-id
|
|
87
|
+
return 'resource_id'; // Android resource-id > durable id= selector (not a text match)
|
|
87
88
|
case 'text':
|
|
88
89
|
return 'text';
|
|
89
90
|
default:
|
|
@@ -130,7 +131,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
130
131
|
const maxScreens = opts.maxScreens ?? 12;
|
|
131
132
|
const depth = opts.depth ?? 3;
|
|
132
133
|
const stopOnAuth = opts.stopOnAuth ?? true;
|
|
133
|
-
//
|
|
134
|
+
// Resolve the generated-data decision once. Only "allowed" when policy + the
|
|
134
135
|
// classified environment (test/staging) permit safe throwaway data; otherwise generation is off.
|
|
135
136
|
const genCtx = opts.allowGeneratedData
|
|
136
137
|
? (() => {
|
|
@@ -155,7 +156,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
155
156
|
promotedSuiteCandidates: 0,
|
|
156
157
|
testabilityBlockers: [],
|
|
157
158
|
};
|
|
158
|
-
const exploredPerScreen = new Map(); // nodeId
|
|
159
|
+
const exploredPerScreen = new Map(); // nodeId > explored signatureKeys
|
|
159
160
|
const skippedNotedScreens = new Set();
|
|
160
161
|
const testabilityNotedScreens = new Set();
|
|
161
162
|
const destructiveCandidates = new Map();
|
|
@@ -164,7 +165,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
164
165
|
let sameScreenEdges = 0;
|
|
165
166
|
let planned = false;
|
|
166
167
|
const note = (n) => sessions.addNote(session, { at: Date.now(), ...n });
|
|
167
|
-
/** Observe the current screen
|
|
168
|
+
/** Observe the current screen as a graph node (structured or visual-only). */
|
|
168
169
|
const observe = async () => {
|
|
169
170
|
const foreground = await driver.foregroundOwner().catch(() => 'unknown');
|
|
170
171
|
let xml = '';
|
|
@@ -176,10 +177,17 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
176
177
|
elements = parsed.elements;
|
|
177
178
|
webviewDominance = parsed.quality.signals.webviewDominance;
|
|
178
179
|
}
|
|
179
|
-
catch {
|
|
180
|
-
|
|
180
|
+
catch (e) {
|
|
181
|
+
// Cancelled job: an aborted dump is not a visual-only screen, a finding or a blocker.
|
|
182
|
+
if (isAbortError(e, signal))
|
|
183
|
+
throw new CancelledError('exploration cancelled');
|
|
184
|
+
/* snapshot failed, so treated as visual-only below */
|
|
181
185
|
}
|
|
186
|
+
if (signal?.aborted)
|
|
187
|
+
throw new CancelledError('exploration cancelled');
|
|
182
188
|
const health = await checkHealth(driver, session.appId, xml || undefined);
|
|
189
|
+
if (health.cancelled || signal?.aborted)
|
|
190
|
+
throw new CancelledError('exploration cancelled');
|
|
183
191
|
await recordHealthFindings(sessions, session, health.findings, driver, health.foreground);
|
|
184
192
|
if (!health.nativeHealthy)
|
|
185
193
|
summary.appErrors += 0; // native handled separately below
|
|
@@ -201,7 +209,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
201
209
|
/* best-effort */
|
|
202
210
|
}
|
|
203
211
|
}
|
|
204
|
-
// Visual-only (Milestone E): no usable structured elements (canvas/map/webview/empty)
|
|
212
|
+
// Visual-only (Milestone E): no usable structured elements (canvas/map/webview/empty) means visual
|
|
205
213
|
// node + assertion, no blind coordinate crawl. A low automation-readiness verdict on a screen
|
|
206
214
|
// that DOES expose identified elements is still structured (we can act on it).
|
|
207
215
|
const visual = elements.length === 0 || webviewDominance > 0.5;
|
|
@@ -257,7 +265,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
257
265
|
workflow: 'guided_exploration',
|
|
258
266
|
outcome: 'pass',
|
|
259
267
|
category: undefined,
|
|
260
|
-
reason: `visual-only screen "${foreground}"
|
|
268
|
+
reason: `visual-only screen "${foreground}", verified by screenshot`,
|
|
261
269
|
verifiedVisually: true,
|
|
262
270
|
method: 'visual',
|
|
263
271
|
artifactUris: screenshotUri ? [screenshotUri] : undefined,
|
|
@@ -268,7 +276,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
268
276
|
const allowedCandidate = (candidate, node) => {
|
|
269
277
|
if (candidate.risk !== 'destructive')
|
|
270
278
|
return allowedUnder(candidate.risk, safeMode);
|
|
271
|
-
// Controlled account-cycle exception
|
|
279
|
+
// Controlled account-cycle exception: logout (and only logout) is an expected step
|
|
272
280
|
// on a disposable generated account inside the mobile-audit account-cycle workflow.
|
|
273
281
|
if (opts.accountCycle?.enabled &&
|
|
274
282
|
allowsControlledLogout({ label: candidate.label, id: candidate.locator?.value, role: candidate.locator?.strategy }, { accountCycle: true, disposableAccount: opts.accountCycle.disposableAccount })) {
|
|
@@ -307,319 +315,328 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
307
315
|
};
|
|
308
316
|
// ---- main loop ----
|
|
309
317
|
let stoppedReason = 'exploration complete';
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
goal: opts.goal,
|
|
346
|
-
screenText,
|
|
347
|
-
fixtures: session.fixtures.map((f) => f.name),
|
|
348
|
-
candidates: obs.candidates,
|
|
349
|
-
});
|
|
350
|
-
graph.setTasks(tasks);
|
|
351
|
-
graph.setHypotheses([`domain:${domain.domain}`, ...tasks.map((t) => `${t.feature}:${t.title}`)]);
|
|
352
|
-
graph.setBlockedPreconditions([...new Set(tasks.flatMap((t) => t.preconditions))]);
|
|
353
|
-
}
|
|
354
|
-
// App error → record + stop this path (do not hide bugs; do not keep crawling a broken screen).
|
|
355
|
-
if (obs.node.health.app === 'error') {
|
|
356
|
-
note({
|
|
357
|
-
workflow: 'guided_exploration',
|
|
358
|
-
outcome: 'fail',
|
|
359
|
-
category: 'app_bug',
|
|
360
|
-
reason: `app-layer error on "${obs.node.title}"`,
|
|
361
|
-
artifactUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : undefined,
|
|
362
|
-
});
|
|
363
|
-
summary.blockers++;
|
|
364
|
-
if (lastNodeId)
|
|
365
|
-
graph.addEdge({
|
|
366
|
-
from: lastNodeId,
|
|
367
|
-
to: obs.node.id,
|
|
368
|
-
action: { type: 'tap', targetDescription: 'prev action' },
|
|
369
|
-
outcome: 'app_error',
|
|
370
|
-
evidenceUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : [],
|
|
318
|
+
// A cancelled job unwinds via CancelledError (thrown by observe). It's never recorded as a finding,
|
|
319
|
+
// blocker or visual-only screen; the partial graph is still finalized below.
|
|
320
|
+
try {
|
|
321
|
+
while (true) {
|
|
322
|
+
if (signal?.aborted) {
|
|
323
|
+
stoppedReason = 'cancelled';
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
if (summary.actionsTried >= maxActions) {
|
|
327
|
+
stoppedReason = `action budget reached (${maxActions})`;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
if (graph.nodeCount() >= maxScreens) {
|
|
331
|
+
stoppedReason = `screen budget reached (${maxScreens})`;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
if (Date.now() >= deadline) {
|
|
335
|
+
stoppedReason = 'time budget reached';
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
const budgetStop = sessions.budgetStop(session);
|
|
339
|
+
if (budgetStop) {
|
|
340
|
+
stoppedReason = `session budget: ${budgetStop}`;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
const obs = await observe();
|
|
344
|
+
progress(`exploring: ${summary.screensVisited} screens, ${summary.actionsTried} actions${obs.visual ? ', visual-only' : ''}`);
|
|
345
|
+
if (!planned && (opts.strategy === 'task_planner' || opts.strategy === 'hybrid')) {
|
|
346
|
+
planned = true;
|
|
347
|
+
const screenText = obs.candidates.map((c) => `${c.label ?? ''} ${c.locator?.value ?? ''}`).join(' ');
|
|
348
|
+
const domain = inferAppDomain({
|
|
349
|
+
packageId: session.appId,
|
|
350
|
+
goal: opts.goal,
|
|
351
|
+
screenText,
|
|
352
|
+
fixtures: session.fixtures.map((f) => f.name),
|
|
371
353
|
});
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
note({
|
|
383
|
-
workflow: 'guided_exploration',
|
|
384
|
-
outcome: 'blocked',
|
|
385
|
-
category: 'missing_test_data',
|
|
386
|
-
reason: 'login required, no credentials available',
|
|
387
|
-
missingPrecondition: 'test account credentials',
|
|
388
|
-
recommendedSetup: 'Provide TEST_EMAIL/TEST_PASSWORD (qa_continue_from_blocker) or accept pre-login coverage.',
|
|
389
|
-
});
|
|
390
|
-
summary.blockers++;
|
|
391
|
-
if (stopOnAuth) {
|
|
392
|
-
const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, ['auth wall blocked exploration']);
|
|
393
|
-
return {
|
|
394
|
-
graph,
|
|
395
|
-
summary,
|
|
396
|
-
needsInput: NeedsInput.credentials('Exploration hit a login screen.'),
|
|
397
|
-
state: 'needs_input',
|
|
398
|
-
stoppedReason: 'auth required (credentials missing)',
|
|
399
|
-
suitePromotion,
|
|
400
|
-
destructiveCandidates: [...destructiveCandidates.values()],
|
|
401
|
-
};
|
|
354
|
+
const tasks = proposeTasks({
|
|
355
|
+
packageId: session.appId,
|
|
356
|
+
goal: opts.goal,
|
|
357
|
+
screenText,
|
|
358
|
+
fixtures: session.fixtures.map((f) => f.name),
|
|
359
|
+
candidates: obs.candidates,
|
|
360
|
+
});
|
|
361
|
+
graph.setTasks(tasks);
|
|
362
|
+
graph.setHypotheses([`domain:${domain.domain}`, ...tasks.map((t) => `${t.feature}:${t.title}`)]);
|
|
363
|
+
graph.setBlockedPreconditions([...new Set(tasks.flatMap((t) => t.preconditions))]);
|
|
402
364
|
}
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
const explored = exploredPerScreen.get(obs.node.id) ?? new Set();
|
|
408
|
-
exploredPerScreen.set(obs.node.id, explored);
|
|
409
|
-
// Visual-only screens: no safe structured target → step back to keep the graph connected.
|
|
410
|
-
const pick = obs.candidates.find((c) => !explored.has(c.signatureKey) && allowedCandidate(c, obs.node));
|
|
411
|
-
const blockedCandidates = obs.candidates.filter((c) => !allowedCandidate(c, obs.node));
|
|
412
|
-
if (blockedCandidates.length && !skippedNotedScreens.has(obs.node.id)) {
|
|
413
|
-
skippedNotedScreens.add(obs.node.id);
|
|
414
|
-
const ex = blockedCandidates[0];
|
|
415
|
-
note({
|
|
416
|
-
workflow: 'guided_exploration',
|
|
417
|
-
outcome: 'skipped',
|
|
418
|
-
category: ex.risk === 'destructive' ? 'destructive_refused' : 'other',
|
|
419
|
-
reason: `Skipped ${ex.risk} action "${ex.label ?? ex.locator?.value}": ${ex.reason}`,
|
|
420
|
-
recommendedSetup: ex.risk === 'destructive'
|
|
421
|
-
? 'Run dry_run_destructive to list candidate-bound approvals, then approve one exact candidate with disposable test state.'
|
|
422
|
-
: 'Add accessibilityLabel/testID so the action can be judged + automated.',
|
|
423
|
-
});
|
|
424
|
-
summary.unsafeActionsSkipped += blockedCandidates.length;
|
|
425
|
-
}
|
|
426
|
-
if (!pick) {
|
|
427
|
-
if (obs.actionLikeSkipped.length && !testabilityNotedScreens.has(obs.node.id)) {
|
|
428
|
-
testabilityNotedScreens.add(obs.node.id);
|
|
429
|
-
for (const s of obs.actionLikeSkipped.slice(0, 8)) {
|
|
430
|
-
summary.testabilityBlockers.push({
|
|
431
|
-
screen: obs.node.title ?? 'unknown',
|
|
432
|
-
visibleText: s.visibleText,
|
|
433
|
-
role: s.role,
|
|
434
|
-
bounds: s.bounds,
|
|
435
|
-
clickable: s.clickable,
|
|
436
|
-
recommendation: `"${s.visibleText}" looks tappable but the UI tree marks it non-interactive. Add accessibilityRole="button" and a stable testID or accessibility identifier.`,
|
|
437
|
-
manualCandidate: { x: Math.round(s.bounds.x + s.bounds.w / 2), y: Math.round(s.bounds.y + s.bounds.h / 2) },
|
|
438
|
-
});
|
|
439
|
-
}
|
|
365
|
+
// App error: record + stop this path (do not hide bugs; do not keep crawling a broken screen).
|
|
366
|
+
if (obs.node.health.app === 'error') {
|
|
440
367
|
note({
|
|
441
368
|
workflow: 'guided_exploration',
|
|
442
|
-
outcome: '
|
|
443
|
-
category: '
|
|
444
|
-
reason: `
|
|
445
|
-
|
|
446
|
-
recommendedSetup: `Add accessibilityRole="button" and a stable testID to primary buttons such as "${obs.actionLikeSkipped[0].visibleText}".`,
|
|
369
|
+
outcome: 'fail',
|
|
370
|
+
category: 'app_bug',
|
|
371
|
+
reason: `app-layer error on "${obs.node.title}"`,
|
|
372
|
+
artifactUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : undefined,
|
|
447
373
|
});
|
|
374
|
+
summary.blockers++;
|
|
375
|
+
if (lastNodeId)
|
|
376
|
+
graph.addEdge({
|
|
377
|
+
from: lastNodeId,
|
|
378
|
+
to: obs.node.id,
|
|
379
|
+
action: { type: 'tap', targetDescription: 'prev action' },
|
|
380
|
+
outcome: 'app_error',
|
|
381
|
+
evidenceUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : [],
|
|
382
|
+
});
|
|
383
|
+
stoppedReason = 'app-layer error encountered';
|
|
384
|
+
break;
|
|
448
385
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
from: obs.node.id,
|
|
454
|
-
action: { type: 'back', targetDescription: 'back' },
|
|
455
|
-
outcome: 'changed_screen',
|
|
456
|
-
evidenceUris: [],
|
|
457
|
-
});
|
|
458
|
-
await settle(driver, { timeoutMs: 4000 }).catch(() => { });
|
|
459
|
-
lastNodeId = obs.node.id;
|
|
460
|
-
continue;
|
|
386
|
+
if (obs.node.health.native === 'error') {
|
|
387
|
+
summary.blockers++;
|
|
388
|
+
stoppedReason = 'native error encountered';
|
|
389
|
+
break;
|
|
461
390
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
// ---- act on the chosen candidate ----
|
|
466
|
-
explored.add(pick.signatureKey);
|
|
467
|
-
const cx = (pick.bounds?.x ?? 0) + (pick.bounds?.w ?? 0) / 2;
|
|
468
|
-
const cy = (pick.bounds?.y ?? 0) + (pick.bounds?.h ?? 0) / 2;
|
|
469
|
-
const fromId = obs.node.id;
|
|
470
|
-
// Text entry only with a value source (P1 fix): never type blindly. If none, skip honestly.
|
|
471
|
-
if (pick.actionType === 'type') {
|
|
472
|
-
const v = valueForField(session, pick.label, pick.locator?.value, pick.role, pick.secure, genCtx);
|
|
473
|
-
if (!v) {
|
|
391
|
+
// Auth wall with no credentials.
|
|
392
|
+
if (obs.authWall && !hasCredentials(session)) {
|
|
474
393
|
note({
|
|
475
394
|
workflow: 'guided_exploration',
|
|
476
|
-
outcome: '
|
|
395
|
+
outcome: 'blocked',
|
|
477
396
|
category: 'missing_test_data',
|
|
478
|
-
reason:
|
|
479
|
-
missingPrecondition: '
|
|
480
|
-
recommendedSetup: 'Provide
|
|
397
|
+
reason: 'login required, no credentials available',
|
|
398
|
+
missingPrecondition: 'test account credentials',
|
|
399
|
+
recommendedSetup: 'Provide SWIPIUM_TEST_EMAIL/SWIPIUM_TEST_PASSWORD (qa_continue_from_blocker) or accept pre-login coverage.',
|
|
481
400
|
});
|
|
482
401
|
summary.blockers++;
|
|
402
|
+
if (stopOnAuth) {
|
|
403
|
+
const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, ['auth wall blocked exploration']);
|
|
404
|
+
return {
|
|
405
|
+
graph,
|
|
406
|
+
summary,
|
|
407
|
+
needsInput: NeedsInput.credentials('Exploration hit a login screen.'),
|
|
408
|
+
state: 'needs_input',
|
|
409
|
+
stoppedReason: 'auth required (credentials missing)',
|
|
410
|
+
suitePromotion,
|
|
411
|
+
destructiveCandidates: [...destructiveCandidates.values()],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// stopOnAuth:false: record blocked and stop crawling further (only public screens were reachable).
|
|
415
|
+
stoppedReason = 'auth required: recorded blocked, no credentials to proceed';
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
const explored = exploredPerScreen.get(obs.node.id) ?? new Set();
|
|
419
|
+
exploredPerScreen.set(obs.node.id, explored);
|
|
420
|
+
// Visual-only screens: no safe structured target, so step back to keep the graph connected.
|
|
421
|
+
const pick = obs.candidates.find((c) => !explored.has(c.signatureKey) && allowedCandidate(c, obs.node));
|
|
422
|
+
const blockedCandidates = obs.candidates.filter((c) => !allowedCandidate(c, obs.node));
|
|
423
|
+
if (blockedCandidates.length && !skippedNotedScreens.has(obs.node.id)) {
|
|
424
|
+
skippedNotedScreens.add(obs.node.id);
|
|
425
|
+
const ex = blockedCandidates[0];
|
|
426
|
+
note({
|
|
427
|
+
workflow: 'guided_exploration',
|
|
428
|
+
outcome: 'skipped',
|
|
429
|
+
category: ex.risk === 'destructive' ? 'destructive_refused' : 'other',
|
|
430
|
+
reason: `Skipped ${ex.risk} action "${ex.label ?? ex.locator?.value}": ${ex.reason}`,
|
|
431
|
+
recommendedSetup: ex.risk === 'destructive'
|
|
432
|
+
? 'Run dry_run_destructive to list candidate-bound approvals, then approve one exact candidate with disposable test state.'
|
|
433
|
+
: 'Add accessibilityLabel/testID so the action can be judged + automated.',
|
|
434
|
+
});
|
|
435
|
+
summary.unsafeActionsSkipped += blockedCandidates.length;
|
|
436
|
+
}
|
|
437
|
+
if (!pick) {
|
|
438
|
+
if (obs.actionLikeSkipped.length && !testabilityNotedScreens.has(obs.node.id)) {
|
|
439
|
+
testabilityNotedScreens.add(obs.node.id);
|
|
440
|
+
for (const s of obs.actionLikeSkipped.slice(0, 8)) {
|
|
441
|
+
summary.testabilityBlockers.push({
|
|
442
|
+
screen: obs.node.title ?? 'unknown',
|
|
443
|
+
visibleText: s.visibleText,
|
|
444
|
+
role: s.role,
|
|
445
|
+
bounds: s.bounds,
|
|
446
|
+
clickable: s.clickable,
|
|
447
|
+
recommendation: `"${s.visibleText}" looks tappable but the UI tree marks it non-interactive. Add accessibilityRole="button" and a stable testID or accessibility identifier.`,
|
|
448
|
+
manualCandidate: { x: Math.round(s.bounds.x + s.bounds.w / 2), y: Math.round(s.bounds.y + s.bounds.h / 2) },
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
note({
|
|
452
|
+
workflow: 'guided_exploration',
|
|
453
|
+
outcome: 'blocked',
|
|
454
|
+
category: 'mcp_limitation',
|
|
455
|
+
reason: `No safe actionable elements on "${obs.node.title}". ${obs.actionLikeSkipped.length} visible action-like text element(s) are not clickable in the UI tree.`,
|
|
456
|
+
missingPrecondition: 'tappable controls with accessibilityRole and testID',
|
|
457
|
+
recommendedSetup: `Add accessibilityRole="button" and a stable testID to primary buttons such as "${obs.actionLikeSkipped[0].visibleText}".`,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
// Nothing safe+new here. Go back to keep exploring; if we're at the root with nothing, stop.
|
|
461
|
+
if (lastNodeId && obs.node.id !== graph.rootId && depth > 1) {
|
|
462
|
+
await driver.pressKey('back').catch(() => { });
|
|
463
|
+
graph.addEdge({
|
|
464
|
+
from: obs.node.id,
|
|
465
|
+
action: { type: 'back', targetDescription: 'back' },
|
|
466
|
+
outcome: 'changed_screen',
|
|
467
|
+
evidenceUris: [],
|
|
468
|
+
});
|
|
469
|
+
await settle(driver, { timeoutMs: 4000 }).catch(() => { });
|
|
470
|
+
lastNodeId = obs.node.id;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
stoppedReason = 'no further safe, unexplored actions';
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
// ---- act on the chosen candidate ----
|
|
477
|
+
explored.add(pick.signatureKey);
|
|
478
|
+
const cx = (pick.bounds?.x ?? 0) + (pick.bounds?.w ?? 0) / 2;
|
|
479
|
+
const cy = (pick.bounds?.y ?? 0) + (pick.bounds?.h ?? 0) / 2;
|
|
480
|
+
const fromId = obs.node.id;
|
|
481
|
+
// Text entry only with a value source (P1 fix): never type blindly. If none, skip honestly.
|
|
482
|
+
if (pick.actionType === 'type') {
|
|
483
|
+
const v = valueForField(session, pick.label, pick.locator?.value, pick.role, pick.secure, genCtx);
|
|
484
|
+
if (!v) {
|
|
485
|
+
note({
|
|
486
|
+
workflow: 'guided_exploration',
|
|
487
|
+
outcome: 'skipped',
|
|
488
|
+
category: 'missing_test_data',
|
|
489
|
+
reason: `text field "${pick.label ?? pick.locator?.value}" needs a value to exercise`,
|
|
490
|
+
missingPrecondition: 'a fixture value or credential for this field',
|
|
491
|
+
recommendedSetup: 'Provide credentials (qa_continue_from_blocker) or a fixture { name, value }.',
|
|
492
|
+
});
|
|
493
|
+
summary.blockers++;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
await driver.tapXY(Math.round(cx), Math.round(cy)); // focus the field
|
|
498
|
+
await driver.inputText(v.value);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
graph.addEdge({
|
|
502
|
+
from: fromId,
|
|
503
|
+
action: { type: 'type', targetDescription: pick.label ?? 'field' },
|
|
504
|
+
outcome: 'blocked',
|
|
505
|
+
evidenceUris: [],
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
summary.actionsTried++;
|
|
510
|
+
sessions.bump(session, 'actions');
|
|
511
|
+
if (v.secret)
|
|
512
|
+
session.secrets.add(v.value);
|
|
513
|
+
if (v.source === 'generator') {
|
|
514
|
+
const rec = session.generatedValues.find((g) => g.varName === v.varName && g.fixture === v.fixture && g.field === v.field);
|
|
515
|
+
if (rec && !rec.artifactUri) {
|
|
516
|
+
rec.artifactUri = sessions.saveArtifact(session, 'generated_data', `generated-${v.varName.toLowerCase()}-${Date.now()}.json`, JSON.stringify({
|
|
517
|
+
schema: 'swipium.generated_value.v1',
|
|
518
|
+
fixture: rec.fixture,
|
|
519
|
+
field: rec.field,
|
|
520
|
+
varName: rec.varName,
|
|
521
|
+
generator: rec.generator,
|
|
522
|
+
value: rec.secret ? '<redacted>' : rec.value,
|
|
523
|
+
secret: rec.secret,
|
|
524
|
+
}, null, 2), 'application/json', `generated fixture value ${rec.fixture}.${rec.field}`);
|
|
525
|
+
sessions.persist(session);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (pick.locator && pick.locator.strategy !== 'coordinate') {
|
|
529
|
+
sessions.addRecordedAction(session, {
|
|
530
|
+
at: Date.now(),
|
|
531
|
+
action: 'type',
|
|
532
|
+
selector: pick.locator.value,
|
|
533
|
+
selectorKind: strategyToKind(pick.locator.strategy),
|
|
534
|
+
text: `\${${v.varName}}`,
|
|
535
|
+
secret: v.secret,
|
|
536
|
+
exportability: v.secret ? 'needs-human-data' : 'semantic',
|
|
537
|
+
screen: obs.node.title,
|
|
538
|
+
provenance: provenanceForCandidate(obs.node, pick),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
542
|
+
const afterType = await observe();
|
|
543
|
+
graph.addEdge({
|
|
544
|
+
from: fromId,
|
|
545
|
+
to: afterType.node.id,
|
|
546
|
+
action: { type: 'type', targetDescription: pick.label ?? 'field' },
|
|
547
|
+
outcome: afterType.node.id === fromId ? 'same_screen' : 'changed_screen',
|
|
548
|
+
evidenceUris: [],
|
|
549
|
+
});
|
|
550
|
+
lastNodeId = afterType.node.id;
|
|
483
551
|
continue;
|
|
484
552
|
}
|
|
485
553
|
try {
|
|
486
|
-
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
487
|
-
await driver.inputText(v.value);
|
|
554
|
+
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
488
555
|
}
|
|
489
556
|
catch {
|
|
557
|
+
if (pick.risk === 'destructive') {
|
|
558
|
+
sessions.recordMutation(session, {
|
|
559
|
+
tool: 'qa_explore',
|
|
560
|
+
action: 'destructive_ui_candidate',
|
|
561
|
+
risk: 'high',
|
|
562
|
+
target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
|
|
563
|
+
consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
|
|
564
|
+
status: 'blocked',
|
|
565
|
+
detail: 'Approved destructive exploration candidate could not be tapped.',
|
|
566
|
+
});
|
|
567
|
+
}
|
|
490
568
|
graph.addEdge({
|
|
491
569
|
from: fromId,
|
|
492
|
-
action: { type: '
|
|
570
|
+
action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control' },
|
|
493
571
|
outcome: 'blocked',
|
|
494
572
|
evidenceUris: [],
|
|
495
573
|
});
|
|
496
574
|
continue;
|
|
497
575
|
}
|
|
576
|
+
if (pick.risk === 'destructive') {
|
|
577
|
+
sessions.recordMutation(session, {
|
|
578
|
+
tool: 'qa_explore',
|
|
579
|
+
action: 'destructive_ui_candidate',
|
|
580
|
+
risk: 'high',
|
|
581
|
+
target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
|
|
582
|
+
consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
|
|
583
|
+
status: 'executed',
|
|
584
|
+
detail: 'Approved destructive exploration candidate tapped.',
|
|
585
|
+
});
|
|
586
|
+
}
|
|
498
587
|
summary.actionsTried++;
|
|
499
588
|
sessions.bump(session, 'actions');
|
|
500
|
-
|
|
501
|
-
session.secrets.add(v.value);
|
|
502
|
-
if (v.source === 'generator') {
|
|
503
|
-
const rec = session.generatedValues.find((g) => g.varName === v.varName && g.fixture === v.fixture && g.field === v.field);
|
|
504
|
-
if (rec && !rec.artifactUri) {
|
|
505
|
-
rec.artifactUri = sessions.saveArtifact(session, 'generated_data', `generated-${v.varName.toLowerCase()}-${Date.now()}.json`, JSON.stringify({
|
|
506
|
-
schema: 'swipium.generated_value.v1',
|
|
507
|
-
fixture: rec.fixture,
|
|
508
|
-
field: rec.field,
|
|
509
|
-
varName: rec.varName,
|
|
510
|
-
generator: rec.generator,
|
|
511
|
-
value: rec.secret ? '<redacted>' : rec.value,
|
|
512
|
-
secret: rec.secret,
|
|
513
|
-
}, null, 2), 'application/json', `generated fixture value ${rec.fixture}.${rec.field}`);
|
|
514
|
-
sessions.persist(session);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
589
|
+
// Record durable taps so qa_generate target:"suite" can promote the path (§9.3).
|
|
517
590
|
if (pick.locator && pick.locator.strategy !== 'coordinate') {
|
|
518
591
|
sessions.addRecordedAction(session, {
|
|
519
592
|
at: Date.now(),
|
|
520
|
-
action: '
|
|
593
|
+
action: 'tap',
|
|
521
594
|
selector: pick.locator.value,
|
|
522
595
|
selectorKind: strategyToKind(pick.locator.strategy),
|
|
523
|
-
|
|
524
|
-
secret: v.secret,
|
|
525
|
-
exportability: v.secret ? 'needs-human-data' : 'semantic',
|
|
596
|
+
exportability: 'semantic',
|
|
526
597
|
screen: obs.node.title,
|
|
527
598
|
provenance: provenanceForCandidate(obs.node, pick),
|
|
528
599
|
});
|
|
529
600
|
}
|
|
530
601
|
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
evidenceUris: [],
|
|
538
|
-
});
|
|
539
|
-
lastNodeId = afterType.node.id;
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
try {
|
|
543
|
-
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
if (pick.risk === 'destructive') {
|
|
547
|
-
sessions.recordMutation(session, {
|
|
548
|
-
tool: 'qa_explore',
|
|
549
|
-
action: 'destructive_ui_candidate',
|
|
550
|
-
risk: 'high',
|
|
551
|
-
target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
|
|
552
|
-
consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
|
|
553
|
-
status: 'blocked',
|
|
554
|
-
detail: 'Approved destructive exploration candidate could not be tapped.',
|
|
555
|
-
});
|
|
602
|
+
// ---- observe outcome to classify the transition ----
|
|
603
|
+
const after = await observe();
|
|
604
|
+
const outcome = after.node.id === fromId ? 'same_screen' : 'changed_screen';
|
|
605
|
+
if (outcome === 'changed_screen') {
|
|
606
|
+
summary.workflowsFound++;
|
|
607
|
+
consecutiveNoChange = 0;
|
|
556
608
|
}
|
|
609
|
+
else {
|
|
610
|
+
consecutiveNoChange++;
|
|
611
|
+
}
|
|
612
|
+
if (outcome === 'same_screen')
|
|
613
|
+
sameScreenEdges++;
|
|
557
614
|
graph.addEdge({
|
|
558
615
|
from: fromId,
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
|
|
572
|
-
status: 'executed',
|
|
573
|
-
detail: 'Approved destructive exploration candidate tapped.',
|
|
616
|
+
to: after.node.id,
|
|
617
|
+
action: {
|
|
618
|
+
type: 'tap',
|
|
619
|
+
targetDescription: pick.label ?? pick.locator?.value ?? 'control',
|
|
620
|
+
locator: pick.locator,
|
|
621
|
+
},
|
|
622
|
+
outcome,
|
|
623
|
+
evidenceUris: after.node.screenshotUri ? [after.node.screenshotUri] : [],
|
|
624
|
+
riskDecision: pick.riskClass ?? pick.risk,
|
|
625
|
+
preActionState: obs.node.signature,
|
|
626
|
+
postActionState: after.node.signature,
|
|
627
|
+
oracle: outcome === 'changed_screen' ? 'screen_signature_changed' : 'screen_signature_same',
|
|
574
628
|
});
|
|
629
|
+
lastNodeId = after.node.id;
|
|
630
|
+
if (consecutiveNoChange >= 3) {
|
|
631
|
+
stoppedReason = 'repeated no-change actions';
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
575
634
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
at: Date.now(),
|
|
582
|
-
action: 'tap',
|
|
583
|
-
selector: pick.locator.value,
|
|
584
|
-
selectorKind: strategyToKind(pick.locator.strategy),
|
|
585
|
-
exportability: 'semantic',
|
|
586
|
-
screen: obs.node.title,
|
|
587
|
-
provenance: provenanceForCandidate(obs.node, pick),
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
591
|
-
// ---- observe outcome to classify the transition ----
|
|
592
|
-
const after = await observe();
|
|
593
|
-
const outcome = after.node.id === fromId ? 'same_screen' : 'changed_screen';
|
|
594
|
-
if (outcome === 'changed_screen') {
|
|
595
|
-
summary.workflowsFound++;
|
|
596
|
-
consecutiveNoChange = 0;
|
|
597
|
-
}
|
|
598
|
-
else {
|
|
599
|
-
consecutiveNoChange++;
|
|
600
|
-
}
|
|
601
|
-
if (outcome === 'same_screen')
|
|
602
|
-
sameScreenEdges++;
|
|
603
|
-
graph.addEdge({
|
|
604
|
-
from: fromId,
|
|
605
|
-
to: after.node.id,
|
|
606
|
-
action: {
|
|
607
|
-
type: 'tap',
|
|
608
|
-
targetDescription: pick.label ?? pick.locator?.value ?? 'control',
|
|
609
|
-
locator: pick.locator,
|
|
610
|
-
},
|
|
611
|
-
outcome,
|
|
612
|
-
evidenceUris: after.node.screenshotUri ? [after.node.screenshotUri] : [],
|
|
613
|
-
riskDecision: pick.riskClass ?? pick.risk,
|
|
614
|
-
preActionState: obs.node.signature,
|
|
615
|
-
postActionState: after.node.signature,
|
|
616
|
-
oracle: outcome === 'changed_screen' ? 'screen_signature_changed' : 'screen_signature_same',
|
|
617
|
-
});
|
|
618
|
-
lastNodeId = after.node.id;
|
|
619
|
-
if (consecutiveNoChange >= 3) {
|
|
620
|
-
stoppedReason = 'repeated no-change actions';
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
635
|
+
}
|
|
636
|
+
catch (e) {
|
|
637
|
+
if (!(e instanceof CancelledError) && !(signal?.aborted && isAbortError(e, signal)))
|
|
638
|
+
throw e;
|
|
639
|
+
stoppedReason = 'cancelled';
|
|
623
640
|
}
|
|
624
641
|
const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, []);
|
|
625
642
|
return {
|