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/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);
|
|
@@ -64,7 +65,15 @@ function valueForField(session, label, locatorValue, role, secure, gen) {
|
|
|
64
65
|
session.inputValues.set(varName, g.value);
|
|
65
66
|
if (g.secret)
|
|
66
67
|
session.secrets.add(g.value);
|
|
67
|
-
session.generatedValues.push({
|
|
68
|
+
session.generatedValues.push({
|
|
69
|
+
at: Date.now(),
|
|
70
|
+
fixture: 'generated',
|
|
71
|
+
field,
|
|
72
|
+
varName,
|
|
73
|
+
generator: g.generator,
|
|
74
|
+
value: g.value,
|
|
75
|
+
secret: g.secret,
|
|
76
|
+
});
|
|
68
77
|
return { value: g.value, varName, secret: g.secret, fixture: 'generated', field, source: 'generator' };
|
|
69
78
|
}
|
|
70
79
|
}
|
|
@@ -75,7 +84,7 @@ function strategyToKind(strategy) {
|
|
|
75
84
|
case 'accessibility':
|
|
76
85
|
return 'accessibility_id';
|
|
77
86
|
case 'id':
|
|
78
|
-
return 'resource_id'; // Android resource-id
|
|
87
|
+
return 'resource_id'; // Android resource-id > durable id= selector (not a text match)
|
|
79
88
|
case 'text':
|
|
80
89
|
return 'text';
|
|
81
90
|
default:
|
|
@@ -122,10 +131,13 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
122
131
|
const maxScreens = opts.maxScreens ?? 12;
|
|
123
132
|
const depth = opts.depth ?? 3;
|
|
124
133
|
const stopOnAuth = opts.stopOnAuth ?? true;
|
|
125
|
-
//
|
|
134
|
+
// Resolve the generated-data decision once. Only "allowed" when policy + the
|
|
126
135
|
// classified environment (test/staging) permit safe throwaway data; otherwise generation is off.
|
|
127
136
|
const genCtx = opts.allowGeneratedData
|
|
128
|
-
? (() => {
|
|
137
|
+
? (() => {
|
|
138
|
+
const r = resolveFirstRunPolicy(session, { testDataPolicyPath: opts.testDataPolicyPath, allowGeneratedAccount: true });
|
|
139
|
+
return { allowed: r.decision.allowed, policy: r.policy };
|
|
140
|
+
})()
|
|
129
141
|
: undefined;
|
|
130
142
|
const startedAt = Date.now();
|
|
131
143
|
const deadline = startedAt + (opts.maxDurationMs ?? 6 * 60_000);
|
|
@@ -144,7 +156,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
144
156
|
promotedSuiteCandidates: 0,
|
|
145
157
|
testabilityBlockers: [],
|
|
146
158
|
};
|
|
147
|
-
const exploredPerScreen = new Map(); // nodeId
|
|
159
|
+
const exploredPerScreen = new Map(); // nodeId > explored signatureKeys
|
|
148
160
|
const skippedNotedScreens = new Set();
|
|
149
161
|
const testabilityNotedScreens = new Set();
|
|
150
162
|
const destructiveCandidates = new Map();
|
|
@@ -153,7 +165,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
153
165
|
let sameScreenEdges = 0;
|
|
154
166
|
let planned = false;
|
|
155
167
|
const note = (n) => sessions.addNote(session, { at: Date.now(), ...n });
|
|
156
|
-
/** Observe the current screen
|
|
168
|
+
/** Observe the current screen as a graph node (structured or visual-only). */
|
|
157
169
|
const observe = async () => {
|
|
158
170
|
const foreground = await driver.foregroundOwner().catch(() => 'unknown');
|
|
159
171
|
let xml = '';
|
|
@@ -165,14 +177,24 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
165
177
|
elements = parsed.elements;
|
|
166
178
|
webviewDominance = parsed.quality.signals.webviewDominance;
|
|
167
179
|
}
|
|
168
|
-
catch {
|
|
169
|
-
|
|
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 */
|
|
170
185
|
}
|
|
186
|
+
if (signal?.aborted)
|
|
187
|
+
throw new CancelledError('exploration cancelled');
|
|
171
188
|
const health = await checkHealth(driver, session.appId, xml || undefined);
|
|
189
|
+
if (health.cancelled || signal?.aborted)
|
|
190
|
+
throw new CancelledError('exploration cancelled');
|
|
172
191
|
await recordHealthFindings(sessions, session, health.findings, driver, health.foreground);
|
|
173
192
|
if (!health.nativeHealthy)
|
|
174
193
|
summary.appErrors += 0; // native handled separately below
|
|
175
|
-
const healthSnap = {
|
|
194
|
+
const healthSnap = {
|
|
195
|
+
native: (health.nativeHealthy ? 'OK' : 'error'),
|
|
196
|
+
app: health.appStatus === 'error' ? 'error' : health.appStatus === 'degraded' ? 'degraded' : 'OK',
|
|
197
|
+
};
|
|
176
198
|
let screenshotUri;
|
|
177
199
|
let png;
|
|
178
200
|
// Respect the SESSION screenshot budget (not a separate fixed counter) so two observes per
|
|
@@ -187,7 +209,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
187
209
|
/* best-effort */
|
|
188
210
|
}
|
|
189
211
|
}
|
|
190
|
-
// 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
|
|
191
213
|
// node + assertion, no blind coordinate crawl. A low automation-readiness verdict on a screen
|
|
192
214
|
// that DOES expose identified elements is still structured (we can act on it).
|
|
193
215
|
const visual = elements.length === 0 || webviewDominance > 0.5;
|
|
@@ -216,7 +238,20 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
216
238
|
authState: authWall ? 'auth_required' : undefined,
|
|
217
239
|
visualOnlyReason: visual ? 'no reliable UI tree (canvas/map/webview or empty snapshot)' : undefined,
|
|
218
240
|
locatorQuality: visual ? undefined : locatorQuality(elements),
|
|
219
|
-
elements: candidates.map((c) => ({
|
|
241
|
+
elements: candidates.map((c) => ({
|
|
242
|
+
ref: c.ref,
|
|
243
|
+
candidateSignature: c.signatureKey,
|
|
244
|
+
label: c.label,
|
|
245
|
+
role: c.role,
|
|
246
|
+
bounds: c.bounds,
|
|
247
|
+
locator: c.locator,
|
|
248
|
+
actionType: c.actionType,
|
|
249
|
+
risk: c.risk,
|
|
250
|
+
riskClass: c.riskClass,
|
|
251
|
+
stepUp: c.stepUp,
|
|
252
|
+
requiresTwoStepConfirmation: c.requiresTwoStepConfirmation,
|
|
253
|
+
reason: c.reason,
|
|
254
|
+
})),
|
|
220
255
|
});
|
|
221
256
|
if (isNew)
|
|
222
257
|
summary.screensVisited++;
|
|
@@ -226,14 +261,22 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
226
261
|
summary.appErrors++;
|
|
227
262
|
// Visual-only screens get a recorded visual assertion as their oracle (Milestone E).
|
|
228
263
|
if (visual && isNew) {
|
|
229
|
-
note({
|
|
264
|
+
note({
|
|
265
|
+
workflow: 'guided_exploration',
|
|
266
|
+
outcome: 'pass',
|
|
267
|
+
category: undefined,
|
|
268
|
+
reason: `visual-only screen "${foreground}", verified by screenshot`,
|
|
269
|
+
verifiedVisually: true,
|
|
270
|
+
method: 'visual',
|
|
271
|
+
artifactUris: screenshotUri ? [screenshotUri] : undefined,
|
|
272
|
+
});
|
|
230
273
|
}
|
|
231
274
|
return { node, isNew, candidates, visual, authWall, actionLikeSkipped };
|
|
232
275
|
};
|
|
233
276
|
const allowedCandidate = (candidate, node) => {
|
|
234
277
|
if (candidate.risk !== 'destructive')
|
|
235
278
|
return allowedUnder(candidate.risk, safeMode);
|
|
236
|
-
// Controlled account-cycle exception
|
|
279
|
+
// Controlled account-cycle exception: logout (and only logout) is an expected step
|
|
237
280
|
// on a disposable generated account inside the mobile-audit account-cycle workflow.
|
|
238
281
|
if (opts.accountCycle?.enabled &&
|
|
239
282
|
allowsControlledLogout({ label: candidate.label, id: candidate.locator?.value, role: candidate.locator?.strategy }, { accountCycle: true, disposableAccount: opts.accountCycle.disposableAccount })) {
|
|
@@ -272,155 +315,264 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
272
315
|
};
|
|
273
316
|
// ---- main loop ----
|
|
274
317
|
let stoppedReason = 'exploration complete';
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
if (graph.nodeCount() >= maxScreens) {
|
|
285
|
-
stoppedReason = `screen budget reached (${maxScreens})`;
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
if (Date.now() >= deadline) {
|
|
289
|
-
stoppedReason = 'time budget reached';
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
const budgetStop = sessions.budgetStop(session);
|
|
293
|
-
if (budgetStop) {
|
|
294
|
-
stoppedReason = `session budget: ${budgetStop}`;
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
const obs = await observe();
|
|
298
|
-
progress(`exploring: ${summary.screensVisited} screens, ${summary.actionsTried} actions${obs.visual ? ', visual-only' : ''}`);
|
|
299
|
-
if (!planned && (opts.strategy === 'task_planner' || opts.strategy === 'hybrid')) {
|
|
300
|
-
planned = true;
|
|
301
|
-
const screenText = obs.candidates.map((c) => `${c.label ?? ''} ${c.locator?.value ?? ''}`).join(' ');
|
|
302
|
-
const domain = inferAppDomain({ packageId: session.appId, goal: opts.goal, screenText, fixtures: session.fixtures.map((f) => f.name) });
|
|
303
|
-
const tasks = proposeTasks({ packageId: session.appId, goal: opts.goal, screenText, fixtures: session.fixtures.map((f) => f.name), candidates: obs.candidates });
|
|
304
|
-
graph.setTasks(tasks);
|
|
305
|
-
graph.setHypotheses([`domain:${domain.domain}`, ...tasks.map((t) => `${t.feature}:${t.title}`)]);
|
|
306
|
-
graph.setBlockedPreconditions([...new Set(tasks.flatMap((t) => t.preconditions))]);
|
|
307
|
-
}
|
|
308
|
-
// App error → record + stop this path (do not hide bugs; do not keep crawling a broken screen).
|
|
309
|
-
if (obs.node.health.app === 'error') {
|
|
310
|
-
note({ workflow: 'guided_exploration', outcome: 'fail', category: 'app_bug', reason: `app-layer error on "${obs.node.title}"`, artifactUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : undefined });
|
|
311
|
-
summary.blockers++;
|
|
312
|
-
if (lastNodeId)
|
|
313
|
-
graph.addEdge({ from: lastNodeId, to: obs.node.id, action: { type: 'tap', targetDescription: 'prev action' }, outcome: 'app_error', evidenceUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : [] });
|
|
314
|
-
stoppedReason = 'app-layer error encountered';
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
if (obs.node.health.native === 'error') {
|
|
318
|
-
summary.blockers++;
|
|
319
|
-
stoppedReason = 'native error encountered';
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
// Auth wall with no credentials.
|
|
323
|
-
if (obs.authWall && !hasCredentials(session)) {
|
|
324
|
-
note({ workflow: 'guided_exploration', outcome: 'blocked', category: 'missing_test_data', reason: 'login required, no credentials available', missingPrecondition: 'test account credentials', recommendedSetup: 'Provide TEST_EMAIL/TEST_PASSWORD (qa_continue_from_blocker) or accept pre-login coverage.' });
|
|
325
|
-
summary.blockers++;
|
|
326
|
-
if (stopOnAuth) {
|
|
327
|
-
const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, ['auth wall blocked exploration']);
|
|
328
|
-
return { graph, summary, needsInput: NeedsInput.credentials('Exploration hit a login screen.'), state: 'needs_input', stoppedReason: 'auth required (credentials missing)', suitePromotion, destructiveCandidates: [...destructiveCandidates.values()] };
|
|
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;
|
|
329
325
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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),
|
|
353
|
+
});
|
|
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))]);
|
|
364
|
+
}
|
|
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') {
|
|
367
|
+
note({
|
|
368
|
+
workflow: 'guided_exploration',
|
|
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,
|
|
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] : [],
|
|
357
382
|
});
|
|
358
|
-
|
|
383
|
+
stoppedReason = 'app-layer error encountered';
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
if (obs.node.health.native === 'error') {
|
|
387
|
+
summary.blockers++;
|
|
388
|
+
stoppedReason = 'native error encountered';
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
// Auth wall with no credentials.
|
|
392
|
+
if (obs.authWall && !hasCredentials(session)) {
|
|
359
393
|
note({
|
|
360
394
|
workflow: 'guided_exploration',
|
|
361
395
|
outcome: 'blocked',
|
|
362
|
-
category: '
|
|
363
|
-
reason:
|
|
364
|
-
missingPrecondition: '
|
|
365
|
-
recommendedSetup:
|
|
396
|
+
category: 'missing_test_data',
|
|
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.',
|
|
366
400
|
});
|
|
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;
|
|
367
417
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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;
|
|
375
436
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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;
|
|
390
551
|
continue;
|
|
391
552
|
}
|
|
392
553
|
try {
|
|
393
|
-
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
394
|
-
await driver.inputText(v.value);
|
|
554
|
+
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
395
555
|
}
|
|
396
556
|
catch {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
rec.artifactUri = sessions.saveArtifact(session, 'generated_data', `generated-${v.varName.toLowerCase()}-${Date.now()}.json`, JSON.stringify({ schema: 'swipium.generated_value.v1', fixture: rec.fixture, field: rec.field, varName: rec.varName, generator: rec.generator, value: rec.secret ? '<redacted>' : rec.value, secret: rec.secret }, null, 2), 'application/json', `generated fixture value ${rec.fixture}.${rec.field}`);
|
|
408
|
-
sessions.persist(session);
|
|
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
|
+
});
|
|
409
567
|
}
|
|
568
|
+
graph.addEdge({
|
|
569
|
+
from: fromId,
|
|
570
|
+
action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control' },
|
|
571
|
+
outcome: 'blocked',
|
|
572
|
+
evidenceUris: [],
|
|
573
|
+
});
|
|
574
|
+
continue;
|
|
410
575
|
}
|
|
411
|
-
if (pick.locator && pick.locator.strategy !== 'coordinate') {
|
|
412
|
-
sessions.addRecordedAction(session, { at: Date.now(), action: 'type', selector: pick.locator.value, selectorKind: strategyToKind(pick.locator.strategy), text: `\${${v.varName}}`, secret: v.secret, exportability: v.secret ? 'needs-human-data' : 'semantic', screen: obs.node.title, provenance: provenanceForCandidate(obs.node, pick) });
|
|
413
|
-
}
|
|
414
|
-
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
415
|
-
const afterType = await observe();
|
|
416
|
-
graph.addEdge({ from: fromId, to: afterType.node.id, action: { type: 'type', targetDescription: pick.label ?? 'field' }, outcome: afterType.node.id === fromId ? 'same_screen' : 'changed_screen', evidenceUris: [] });
|
|
417
|
-
lastNodeId = afterType.node.id;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
try {
|
|
421
|
-
await driver.tapXY(Math.round(cx), Math.round(cy));
|
|
422
|
-
}
|
|
423
|
-
catch {
|
|
424
576
|
if (pick.risk === 'destructive') {
|
|
425
577
|
sessions.recordMutation(session, {
|
|
426
578
|
tool: 'qa_explore',
|
|
@@ -428,52 +580,73 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
|
|
|
428
580
|
risk: 'high',
|
|
429
581
|
target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
|
|
430
582
|
consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
|
|
431
|
-
status: '
|
|
432
|
-
detail: 'Approved destructive exploration candidate
|
|
583
|
+
status: 'executed',
|
|
584
|
+
detail: 'Approved destructive exploration candidate tapped.',
|
|
433
585
|
});
|
|
434
586
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
587
|
+
summary.actionsTried++;
|
|
588
|
+
sessions.bump(session, 'actions');
|
|
589
|
+
// Record durable taps so qa_generate target:"suite" can promote the path (§9.3).
|
|
590
|
+
if (pick.locator && pick.locator.strategy !== 'coordinate') {
|
|
591
|
+
sessions.addRecordedAction(session, {
|
|
592
|
+
at: Date.now(),
|
|
593
|
+
action: 'tap',
|
|
594
|
+
selector: pick.locator.value,
|
|
595
|
+
selectorKind: strategyToKind(pick.locator.strategy),
|
|
596
|
+
exportability: 'semantic',
|
|
597
|
+
screen: obs.node.title,
|
|
598
|
+
provenance: provenanceForCandidate(obs.node, pick),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
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;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
consecutiveNoChange++;
|
|
611
|
+
}
|
|
612
|
+
if (outcome === 'same_screen')
|
|
613
|
+
sameScreenEdges++;
|
|
614
|
+
graph.addEdge({
|
|
615
|
+
from: fromId,
|
|
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',
|
|
447
628
|
});
|
|
629
|
+
lastNodeId = after.node.id;
|
|
630
|
+
if (consecutiveNoChange >= 3) {
|
|
631
|
+
stoppedReason = 'repeated no-change actions';
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
448
634
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
await settle(driver, { timeoutMs: 5000 }).catch(() => { });
|
|
456
|
-
// ---- observe outcome to classify the transition ----
|
|
457
|
-
const after = await observe();
|
|
458
|
-
const outcome = after.node.id === fromId ? 'same_screen' : 'changed_screen';
|
|
459
|
-
if (outcome === 'changed_screen') {
|
|
460
|
-
summary.workflowsFound++;
|
|
461
|
-
consecutiveNoChange = 0;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
consecutiveNoChange++;
|
|
465
|
-
}
|
|
466
|
-
if (outcome === 'same_screen')
|
|
467
|
-
sameScreenEdges++;
|
|
468
|
-
graph.addEdge({ from: fromId, to: after.node.id, action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control', locator: pick.locator }, outcome, evidenceUris: after.node.screenshotUri ? [after.node.screenshotUri] : [], riskDecision: pick.riskClass ?? pick.risk, preActionState: obs.node.signature, postActionState: after.node.signature, oracle: outcome === 'changed_screen' ? 'screen_signature_changed' : 'screen_signature_same' });
|
|
469
|
-
lastNodeId = after.node.id;
|
|
470
|
-
if (consecutiveNoChange >= 3) {
|
|
471
|
-
stoppedReason = 'repeated no-change actions';
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
635
|
+
}
|
|
636
|
+
catch (e) {
|
|
637
|
+
if (!(e instanceof CancelledError) && !(signal?.aborted && isAbortError(e, signal)))
|
|
638
|
+
throw e;
|
|
639
|
+
stoppedReason = 'cancelled';
|
|
474
640
|
}
|
|
475
641
|
const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, []);
|
|
476
|
-
return {
|
|
642
|
+
return {
|
|
643
|
+
graph,
|
|
644
|
+
summary,
|
|
645
|
+
state: summary.appErrors > 0 || summary.blockers > 0 ? 'blocked' : 'completed',
|
|
646
|
+
stoppedReason,
|
|
647
|
+
suitePromotion,
|
|
648
|
+
destructiveCandidates: [...destructiveCandidates.values()],
|
|
649
|
+
};
|
|
477
650
|
}
|
|
478
651
|
function finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, reasons) {
|
|
479
652
|
const nodes = graph.allNodes();
|