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
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# CI reports
|
|
2
|
+
|
|
3
|
+
`swipium report` turns a finished Swipium QA run into files that CI systems read:
|
|
4
|
+
|
|
5
|
+
- `junit`: test-report XML;
|
|
6
|
+
- `sarif`: SARIF 2.1.0 for GitHub code scanning;
|
|
7
|
+
- `github-summary`: Markdown for `$GITHUB_STEP_SUMMARY`;
|
|
8
|
+
- `markdown` and `json`.
|
|
9
|
+
|
|
10
|
+
A CI pipeline built on Swipium has three parts, and only one of them needs an agent:
|
|
11
|
+
|
|
12
|
+
| Part | Needs an agent? | What does it |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Boot an emulator, build the app, install it | No | Your CI (for example `reactivecircus/android-emulator-runner`, Gradle, `adb install`) |
|
|
15
|
+
| **Drive the app and decide what to check** | **Yes** | An MCP client running an LLM (for example Claude Code headless, `claude -p`) that calls Swipium tools and ends with `qa_report` |
|
|
16
|
+
| Render JUnit, SARIF and the summary, and fail the job on the release gate | No | `npx swipium report …`: a deterministic CLI with no device and no LLM |
|
|
17
|
+
|
|
18
|
+
Swipium is an MCP server, not a test runner. Without an MCP client calling its tools, nothing
|
|
19
|
+
exercises the app, and there is no standalone command that runs flows. To replay the same steps on
|
|
20
|
+
every run instead of letting the model decide what to check, have the agent step call `qa_flow_run`
|
|
21
|
+
on committed flows (see [flows.md](flows.md)); that still runs through an MCP client.
|
|
22
|
+
|
|
23
|
+
## `swipium report`
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
swipium report --format <junit|sarif|github-summary|markdown|json>
|
|
27
|
+
[--root <dir>] [--latest | --session <id> | --report <file>]
|
|
28
|
+
[--out <file>] [--fail-on-gate]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- **Which report it reads.** It renders the report JSON that `qa_report` (or the end of a
|
|
32
|
+
`qa_test_this` run) saved for a session. Session state lives under
|
|
33
|
+
`~/.swipium/runs/<project-hash>/<session>/`, so run the CLI on the same runner, as the same user,
|
|
34
|
+
after the agent step, and from the same project root the agent used (or pass `--root`).
|
|
35
|
+
- **`--latest`** is the default. It picks the newest session for `--root` (default: the current
|
|
36
|
+
directory) that has a saved report, and skips sessions that never produced one.
|
|
37
|
+
- **`--session <id>`** picks one session. **`--report <file>`** renders a report JSON you copied
|
|
38
|
+
yourself, for example from a downloaded artifact.
|
|
39
|
+
- **Output.** The file goes to stdout, or to `--out` (parent directories are created). The verdict
|
|
40
|
+
line `Release gate: PASS|BLOCK: <reason>` always goes to stderr.
|
|
41
|
+
- **Exit codes:**
|
|
42
|
+
|
|
43
|
+
| Code | Meaning |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `0` | The file was written. The gate passed, or it blocked but `--fail-on-gate` was not set. |
|
|
46
|
+
| `1` | `--fail-on-gate` was set and the release-gate policy blocks. |
|
|
47
|
+
| `2` | Usage error, unknown format, no session or report found, or the file is not a Swipium report. |
|
|
48
|
+
|
|
49
|
+
- **Secrets.** Raw secret values are never written to disk, so the CLI can't scrub them later.
|
|
50
|
+
Instead, `qa_report` deep-redacts the whole report with the session's registered secrets before
|
|
51
|
+
saving it, and the CLI renders that redacted copy. Every string field is redacted, including step
|
|
52
|
+
summaries, workflow names and next steps.
|
|
53
|
+
|
|
54
|
+
## GitHub Actions recipe (Android)
|
|
55
|
+
|
|
56
|
+
This recipe keeps every Swipium consent out of the agent's hands:
|
|
57
|
+
|
|
58
|
+
- The runner boots the emulator.
|
|
59
|
+
- The workflow installs the APK with `adb`, so it is your decision, made in the workflow.
|
|
60
|
+
- The agent then uses only tools that need no consent: attach to the running emulator, launch the
|
|
61
|
+
installed app, smoke it, and report.
|
|
62
|
+
- `SWIPIUM_REQUIRE_ELICITATION=1` plus `--permission-prompts none` means any unexpected consent
|
|
63
|
+
request is refused instead of approved by the model. The run then reports **blocked** and says
|
|
64
|
+
why.
|
|
65
|
+
|
|
66
|
+
Prerequisites:
|
|
67
|
+
|
|
68
|
+
- The repository builds a debug APK with Gradle. Adjust the build command, `APK` and `APP_ID` for
|
|
69
|
+
your project.
|
|
70
|
+
- `ANTHROPIC_API_KEY` is a repository secret. Claude Code uses it for the agent step, and
|
|
71
|
+
`--bare` requires an API key rather than a subscription login.
|
|
72
|
+
- Claude Code v2.1.259 or later, for `--permission-prompts`. `npm install -g` installs the latest.
|
|
73
|
+
- React Native and Expo projects: `qa_prepare_target` expects Metro to be serving before it
|
|
74
|
+
launches the app. In CI, build a variant with the JS bundle embedded (for example a release build
|
|
75
|
+
signed with a debug key), and add `allowLaunchWithoutMetro: true` to the `qa_prepare_target` call
|
|
76
|
+
in the prompt.
|
|
77
|
+
- For login flows, provide test accounts through `.swipium/fixtures.json` or the
|
|
78
|
+
`SWIPIUM_TEST_EMAIL` / `SWIPIUM_TEST_PASSWORD` secrets passed into the server's `env`. Without
|
|
79
|
+
them, the agent tests what it can reach before login.
|
|
80
|
+
|
|
81
|
+
`.github/workflows/swipium-qa.yml`:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
name: Swipium QA
|
|
85
|
+
on: [pull_request]
|
|
86
|
+
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
checks: write # mikepenz/action-junit-report (add pull-requests: write only if you set comment: true)
|
|
90
|
+
security-events: write # github/codeql-action/upload-sarif
|
|
91
|
+
actions: read # upload-sarif on private repositories
|
|
92
|
+
|
|
93
|
+
jobs:
|
|
94
|
+
qa:
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
timeout-minutes: 45
|
|
97
|
+
env:
|
|
98
|
+
APK: app/build/outputs/apk/debug/app-debug.apk
|
|
99
|
+
APP_ID: com.example.app # your applicationId
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
102
|
+
|
|
103
|
+
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
|
|
104
|
+
with: { distribution: temurin, java-version: '17' }
|
|
105
|
+
|
|
106
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
107
|
+
with: { node-version: '20' }
|
|
108
|
+
|
|
109
|
+
- name: Build debug APK
|
|
110
|
+
run: ./gradlew assembleDebug
|
|
111
|
+
|
|
112
|
+
- name: Install Claude Code
|
|
113
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
114
|
+
|
|
115
|
+
# Hardware acceleration for the emulator (from the android-emulator-runner README).
|
|
116
|
+
- name: Enable KVM
|
|
117
|
+
run: |
|
|
118
|
+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
|
119
|
+
sudo udevadm control --reload-rules
|
|
120
|
+
sudo udevadm trigger --name-match=kvm
|
|
121
|
+
|
|
122
|
+
# The emulator only lives while `script` runs, so the agent step runs INSIDE it.
|
|
123
|
+
# android-emulator-runner runs each `script` line as its own shell command, so the
|
|
124
|
+
# logic lives in a script file.
|
|
125
|
+
- name: Agent QA run (needs an LLM)
|
|
126
|
+
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
|
127
|
+
env:
|
|
128
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
129
|
+
with:
|
|
130
|
+
api-level: 34
|
|
131
|
+
arch: x86_64
|
|
132
|
+
target: google_apis
|
|
133
|
+
disable-animations: true
|
|
134
|
+
script: bash .github/scripts/swipium-agent.sh
|
|
135
|
+
|
|
136
|
+
# Everything below is deterministic: no emulator, no LLM.
|
|
137
|
+
- name: Swipium JUnit
|
|
138
|
+
if: always()
|
|
139
|
+
run: npx -y swipium report --latest --format junit --out swipium/junit.xml
|
|
140
|
+
|
|
141
|
+
- name: Swipium SARIF
|
|
142
|
+
if: always()
|
|
143
|
+
run: npx -y swipium report --latest --format sarif --out swipium/results.sarif
|
|
144
|
+
|
|
145
|
+
- name: Swipium job summary
|
|
146
|
+
if: always()
|
|
147
|
+
run: npx -y swipium report --latest --format github-summary >> "$GITHUB_STEP_SUMMARY"
|
|
148
|
+
|
|
149
|
+
- name: Publish JUnit
|
|
150
|
+
if: always() && hashFiles('swipium/junit.xml') != ''
|
|
151
|
+
uses: mikepenz/action-junit-report@a9170d5795813c01ab4901ffb045b52bab4ab09d # v6.5.0
|
|
152
|
+
with:
|
|
153
|
+
report_paths: swipium/junit.xml
|
|
154
|
+
include_passed: true
|
|
155
|
+
|
|
156
|
+
- name: Upload SARIF to code scanning
|
|
157
|
+
if: always() && hashFiles('swipium/results.sarif') != ''
|
|
158
|
+
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
|
159
|
+
with:
|
|
160
|
+
sarif_file: swipium/results.sarif
|
|
161
|
+
category: swipium
|
|
162
|
+
|
|
163
|
+
# Screenshots and recordings are pixels and are NOT redacted. Drop this step for apps
|
|
164
|
+
# that show real secrets on screen.
|
|
165
|
+
- name: Keep raw evidence
|
|
166
|
+
if: always()
|
|
167
|
+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
168
|
+
with:
|
|
169
|
+
name: swipium-runs
|
|
170
|
+
path: /home/runner/.swipium/runs/ # $HOME on GitHub-hosted ubuntu runners
|
|
171
|
+
retention-days: 14
|
|
172
|
+
|
|
173
|
+
# Last step, so every report above is published before the job fails.
|
|
174
|
+
- name: Release gate
|
|
175
|
+
run: npx -y swipium report --latest --format json --out swipium/report.json --fail-on-gate
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`.github/scripts/swipium-agent.sh`:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
#!/usr/bin/env bash
|
|
182
|
+
set -euo pipefail
|
|
183
|
+
|
|
184
|
+
# 1. Install the app yourself. Swipium's own install path is consent-gated, and nobody can
|
|
185
|
+
# approve a consent in CI.
|
|
186
|
+
adb wait-for-device
|
|
187
|
+
adb install -r -g "$APK"
|
|
188
|
+
|
|
189
|
+
# 2. Swipium as the only MCP server. SWIPIUM_REQUIRE_ELICITATION=1: an unexpected consent is
|
|
190
|
+
# refused (CONSENT_CANCELLED / CONSENT_REFUSED) instead of self-approved by the model.
|
|
191
|
+
cat > "$RUNNER_TEMP/swipium-mcp.json" <<'JSON'
|
|
192
|
+
{ "mcpServers": { "swipium": { "command": "npx", "args": ["-y", "swipium"],
|
|
193
|
+
"env": { "SWIPIUM_REQUIRE_ELICITATION": "1" } } } }
|
|
194
|
+
JSON
|
|
195
|
+
|
|
196
|
+
# 3. The agent step. Every call below works without consent on a booted emulator with the app
|
|
197
|
+
# already installed.
|
|
198
|
+
claude --bare -p "You are running unattended in CI. An Android emulator is booted and $APP_ID is installed.
|
|
199
|
+
Use only the swipium MCP tools. Always pass projectRoot=\"$GITHUB_WORKSPACE\" where a tool accepts it.
|
|
200
|
+
1. qa_start_session { projectRoot, profile: \"full_smoke\" }.
|
|
201
|
+
2. qa_prepare_target { sessionId, appId: \"$APP_ID\" }. If it returns a jobId, poll qa_job_status { sessionId, jobId, waitMs: 60000 } until it is no longer running.
|
|
202
|
+
3. qa_smoke { sessionId }.
|
|
203
|
+
4. If any result is requiresConsent, CONSENT_*, needs_input or blocked, do not try to work around it: record it with qa_note and go to step 5.
|
|
204
|
+
5. qa_report { sessionId, format: \"summary\" } so the report is saved, then stop." \
|
|
205
|
+
--mcp-config "$RUNNER_TEMP/swipium-mcp.json" \
|
|
206
|
+
--allowedTools "mcp__swipium" \
|
|
207
|
+
--permission-mode dontAsk \
|
|
208
|
+
--permission-prompts none \
|
|
209
|
+
--output-format json > "$RUNNER_TEMP/claude-result.json" || true
|
|
210
|
+
|
|
211
|
+
# The agent's exit status does not gate the job. `swipium report --fail-on-gate` does.
|
|
212
|
+
jq -r '.result // empty' "$RUNNER_TEMP/claude-result.json" || true
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Notes on the agent step:
|
|
216
|
+
|
|
217
|
+
- **Claude Code flags.** `claude -p` runs Claude Code without an interactive session.
|
|
218
|
+
- `--bare` skips local hooks, plugins, skills, `CLAUDE.md` and any `.mcp.json` servers, so the
|
|
219
|
+
server from `--mcp-config` is the only MCP server.
|
|
220
|
+
- `--allowedTools "mcp__swipium"` pre-approves every Swipium tool.
|
|
221
|
+
- `--permission-mode dontAsk` denies anything else instead of prompting.
|
|
222
|
+
- `--permission-prompts none` tells Claude nobody can answer prompts, and cancels any MCP
|
|
223
|
+
elicitation request.
|
|
224
|
+
|
|
225
|
+
See the Claude Code docs: [headless](https://code.claude.com/docs/en/headless),
|
|
226
|
+
[CLI reference](https://code.claude.com/docs/en/cli-reference) and
|
|
227
|
+
[permissions](https://code.claude.com/docs/en/permissions).
|
|
228
|
+
- **Why the recipe avoids `qa_test_this` in CI.** Its execute mode requests one combined consent
|
|
229
|
+
for the privileged steps it plans, and every app install is among them. With nobody to approve,
|
|
230
|
+
it would end blocked. The low-level path above attaches to the running emulator, and an app that
|
|
231
|
+
is already installed skips the install consent.
|
|
232
|
+
- **Testing more.** `qa_explore { sessionId }` can follow the smoke. By default it skips
|
|
233
|
+
destructive-looking actions without asking. It asks for consent only when a call names one
|
|
234
|
+
explicit `destructiveCandidate` to run, which the settings above would refuse.
|
|
235
|
+
- **Timing** depends on the model and the app. Treat the first runs as calibration, and set
|
|
236
|
+
`timeout-minutes` from them.
|
|
237
|
+
|
|
238
|
+
## What each file contains
|
|
239
|
+
|
|
240
|
+
**JUnit** has two suites. The first holds one testcase per recorded workflow, the second one per
|
|
241
|
+
finding.
|
|
242
|
+
|
|
243
|
+
- A workflow `fail` becomes `<failure>`. `blocked`, `skipped` and `not_applicable` become
|
|
244
|
+
`<skipped>`.
|
|
245
|
+
- A high-severity finding becomes `<failure>`. Medium and low findings pass, with the detail in
|
|
246
|
+
`<system-out>`.
|
|
247
|
+
- A failure that the release-gate policy only warns on (`warnOn`) or suppresses (`ignoreKnown`)
|
|
248
|
+
becomes `<skipped message="policy warned|suppressed (release gate not blocked): …">`, so a gate
|
|
249
|
+
PASS never shows up as a failed test.
|
|
250
|
+
- The gate verdict (`swipium.releaseGate`) and reason (`swipium.releaseGate.reason`) are testsuite
|
|
251
|
+
`<properties>`.
|
|
252
|
+
- Evidence URIs appear in each testcase's `<system-out>`.
|
|
253
|
+
- XML-illegal control characters, such as ANSI escapes and NULs from logcat, are replaced with
|
|
254
|
+
U+FFFD, so strict parsers accept the file.
|
|
255
|
+
|
|
256
|
+
**SARIF** has one result per finding and one per failed workflow. GitHub code scanning only shows
|
|
257
|
+
results that point at a file in the repository, so each result is anchored to the most specific
|
|
258
|
+
real file Swipium can justify:
|
|
259
|
+
|
|
260
|
+
1. the app-map source file of the screen or workflow involved, when `.swipium/app-map.json` exists
|
|
261
|
+
(see `qa_app_map_update`);
|
|
262
|
+
2. otherwise the project manifest, in this order: `app.json`, `package.json`, `pubspec.yaml`, the
|
|
263
|
+
Gradle app module, an iOS `Info.plist`, then `README.md`.
|
|
264
|
+
|
|
265
|
+
Other SARIF details:
|
|
266
|
+
|
|
267
|
+
- Paths are relative to the repository root, the nearest ancestor with `.git`. In a monorepo an
|
|
268
|
+
anchor reads `apps/mobile/app.json`. Each location has `uriBaseId: %SRCROOT%` and a region on
|
|
269
|
+
line 1.
|
|
270
|
+
- The level follows severity: high is `error`, medium is `warning`, low is `note`. Failed workflows
|
|
271
|
+
are `error`.
|
|
272
|
+
- Screenshots and other `swipium://` evidence go in `relatedLocations` and `properties`.
|
|
273
|
+
- Each result carries its own `partialFingerprints.primaryLocationLineHash`. Volatile ids and
|
|
274
|
+
timestamps are removed before hashing. This keeps distinct results on the same manifest line from
|
|
275
|
+
collapsing into one alert, and keeps alerts stable across runs.
|
|
276
|
+
- `invocations[0].executionSuccessful` is `true` whenever Swipium produced the file. The verdict is
|
|
277
|
+
in `runs[0].properties.releaseGateVerdict` (`pass` or `block`), with the full decision in
|
|
278
|
+
`releaseGate`.
|
|
279
|
+
|
|
280
|
+
See GitHub's
|
|
281
|
+
[SARIF support for code scanning](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning)
|
|
282
|
+
and
|
|
283
|
+
[Uploading a SARIF file](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github).
|
|
284
|
+
|
|
285
|
+
**GitHub summary** contains the verdict, the gate line, finding counts, the top findings, failed or
|
|
286
|
+
blocked workflows, and links to the evidence. Text that comes from the run is escaped so it can't
|
|
287
|
+
inject Markdown or HTML: the app id, device name, policy reason and next action. GitHub rejects a
|
|
288
|
+
step summary over 1 MiB, so the summary stops at about 900 KB and ends with a truncation note.
|
|
289
|
+
|
|
290
|
+
## From inside an agent session (no CLI)
|
|
291
|
+
|
|
292
|
+
`qa_report { sessionId, format: "junit" | "sarif" | "github-summary" }` writes the same file as a
|
|
293
|
+
session artifact and returns its `exportUri`. Read it with `qa_get_artifact { uri }`. This is useful
|
|
294
|
+
when the agent itself publishes the results. In CI, prefer the CLI: it doesn't depend on the agent
|
|
295
|
+
remembering to write files.
|
|
296
|
+
|
|
297
|
+
## Release-gate policy
|
|
298
|
+
|
|
299
|
+
`.swipium/policy.json` decides the verdict in every export and the `--fail-on-gate` exit code. The
|
|
300
|
+
file's other settings, such as `ciAllowMutations`, are described in
|
|
301
|
+
[flows.md](flows.md#ci-policy). The gate looks at every failed workflow and every high-severity
|
|
302
|
+
finding:
|
|
303
|
+
|
|
304
|
+
- A failed workflow's code is the failure code of its first failing step, else its category, else
|
|
305
|
+
`UNKNOWN`.
|
|
306
|
+
- A finding's code is its failure code, else its kind.
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"blockOn": ["native_crash", "anr", "error_boundary"],
|
|
311
|
+
"warnOn": ["missing_test_data", "assertion_failed"],
|
|
312
|
+
"ignoreKnown": []
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Each failure is checked in this order:
|
|
317
|
+
|
|
318
|
+
1. **No policy file, or one that doesn't parse**: every failure blocks.
|
|
319
|
+
2. **`ignoreKnown`**: an exact code match (case and punctuation ignored) is suppressed.
|
|
320
|
+
3. **`blockOn`**: a matching code blocks. An empty or missing `blockOn` blocks every failure. The
|
|
321
|
+
token `failed_required_flow` matches every failure, so with it in `blockOn`, `warnOn` never
|
|
322
|
+
applies.
|
|
323
|
+
4. **`warnOn`**: a matching code only warns.
|
|
324
|
+
5. **Anything else blocks.** A failure the policy doesn't mention is not ignored.
|
|
325
|
+
|
|
326
|
+
Tokens match failure codes case-insensitively, with punctuation ignored (`native_crash` matches
|
|
327
|
+
`NATIVE_CRASH`). A few synonyms also work: `crash` means `NATIVE_CRASH`, `app_bug` and
|
|
328
|
+
`visual_diff` mean `ASSERTION_FAILED`. `ciAllowMutations` does not affect the gate.
|
package/docs/concepts.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Concepts
|
|
2
|
+
|
|
3
|
+
The ideas that cut across Swipium's tools: sessions and jobs, how the project root is found, consent, secret handling, iOS modes, and device policy. Per-tool parameters and failure codes are in the [Tool Reference](tools.md); flow files are in [Flows](flows.md); environment variables are in the [README](../README.md#configuration--environment-variables).
|
|
4
|
+
|
|
5
|
+
**Contents**: [Sessions and jobs](#sessions-and-jobs) · [Project root](#project-root) · [Consent](#consent) · [Secrets and redaction](#secrets-and-redaction) · [iOS modes](#ios-modes) · [Devices](#devices) · [Glossary](#glossary)
|
|
6
|
+
|
|
7
|
+
## Sessions and jobs
|
|
8
|
+
|
|
9
|
+
### Sessions
|
|
10
|
+
|
|
11
|
+
A session holds one run's device, app, budget, recorded actions, findings, notes, and evidence. `qa_test_this` creates its own; the low-level tools need one from `qa_start_session`. Every tool that takes a `sessionId` returns `INVALID_ARGUMENT` (`Unknown sessionId "…"`) for an unknown id, with nothing run.
|
|
12
|
+
|
|
13
|
+
- **Location**: each session lives in `~/.swipium/runs/<project-hash>/<sessionId>/`, with `state.json` and artifact folders inside. Files are written atomically with mode 0600 (directories 0700). `~/.swipium/registry.json` lists up to 200 reloadable sessions.
|
|
14
|
+
- **What is persisted**: notes, findings, tool errors, jobs, environment changes, and mutations, all redacted with the session's secrets before writing. Recorded actions are persisted in their secret-safe form (see [Secrets and redaction](#secrets-and-redaction)). For fixtures only metadata is written: fixture values, field values, and seed specs never are, and secret generated values are stored as `<redacted>`.
|
|
15
|
+
- **What is never persisted**: secret values and the values of supplied inputs (credentials, OTPs). They live in memory only, so after a server restart a login run asks for them again even though the stored metadata still lists them.
|
|
16
|
+
- **Rehydration**: after a restart, a session reloads on first use. The device, app id, `driverKind`, `wdaUrl`, jobs, artifacts, findings, notes, and mutations are restored. The device is re-attached with the saved driver kind (WDA when the session had attached it and it is still reachable); an offline device is never replaced by a different one. Fixtures are re-read from `.swipium/fixtures.json`, which supplies the live values. A fixture that was passed inline to `qa_start_session` and is not in that file comes back as metadata only, **without its `value`, field values, or `seed`**; re-declare it (or add it to `fixtures.json`) if a run needs them. If the old state shows secret activity, the session is flagged `redactionDegraded`, because the secret set cannot be rebuilt.
|
|
17
|
+
- **Retention**: at startup, session directories are pruned when all of these hold: last activity older than `SWIPIUM_RETENTION_DAYS` (default 30; `0` or `off` disables the automatic prune), not in the registry, not live in this process, and not among the newest `SWIPIUM_RETENTION_KEEP` (default 20) sessions of that project. The prune fails closed: when `registry.json` exists but cannot be read or parsed, nothing is deleted. `swipium gc` applies the same rule on demand.
|
|
18
|
+
- **Artifacts** are addressed as `swipium://session/{sessionId}/{kind}/{name}` and read with `qa_get_artifact` or as MCP resources.
|
|
19
|
+
|
|
20
|
+
### Jobs
|
|
21
|
+
|
|
22
|
+
Long operations run as background jobs and return a `jobId` at once: `qa_test_this` and `qa_test_feature` in `execute` or `interactive` mode, `qa_explore`, `qa_build` in `run` mode, `qa_bundletool`, and the boot and install steps of `qa_prepare_target`.
|
|
23
|
+
|
|
24
|
+
The usual loop:
|
|
25
|
+
|
|
26
|
+
1. `qa_test_this {mode:"execute"}` (after any consent) returns `{sessionId, jobId, state:"running"}`.
|
|
27
|
+
2. `qa_job_status {sessionId, jobId, waitMs:60000}` until `status` is no longer `running`.
|
|
28
|
+
3. On `result.state:"completed"`, read the report with `qa_get_artifact {uri: result.reportUri}`.
|
|
29
|
+
|
|
30
|
+
**Job status and result state are different fields.**
|
|
31
|
+
|
|
32
|
+
| Field | Values | Meaning |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `status` (every job) | `running`, `done`, `failed`, `cancelled` | Where the job is. |
|
|
35
|
+
| `result.state` (`qa_test_this` jobs) | `completed`, `blocked`, `unsafe`, `needs_input` | How the run ended. `completed` and `needs_input` end the job `done`; `blocked` and `unsafe` end it `failed`. The envelope is in `result` either way. |
|
|
36
|
+
|
|
37
|
+
- **needs_input**: there is no `needs_input` job status. A job that stops on a question ends `done` with `result.state:"needs_input"`. `qa_test_this` can also return `state:"needs_input"` **directly, with no `jobId`**: in `interactive` mode, or with `stopOnNeedsInput` or `goal:"test_login"`, the credentials question is asked before any job starts when the project likely has a login and no credentials are available. Either way, relay the one question and make the returned resume call (`qa_continue_from_blocker`).
|
|
38
|
+
- **Polling**: `qa_job_status {sessionId, jobId, waitMs}` returns `{jobId, kind, status, progress, progressDetail, error, result, artifactUris}`. `waitMs` (default 0, capped at 120000) long-polls: the call returns as soon as the job leaves `running` and adds `waited:{waitedMs, timedOut}`. An unknown `jobId` is `INVALID_ARGUMENT`.
|
|
39
|
+
- **Blocking instead of polling**: `qa_test_this {waitForCompletion:true}` waits up to `timeoutMs` (default 120000) and returns the terminal result directly, or `state:"running"` with `timedOutWaiting:true`.
|
|
40
|
+
- **Cancelling**: `qa_job_cancel {sessionId, jobId}` returns `{jobId, cancelled}`; `cancelled:false` means the job had already finished or is unknown. It aborts child processes (build, boot, install, record). A cancelled job has status `cancelled` and no `result`. Side effects already applied are not rolled back, and a worker never overwrites a cancelled job's status.
|
|
41
|
+
- **Restarts**: jobs are persisted with the session. A job that was `running` when the server stopped is marked `failed` with `server restarted while job was running (child process gone)`.
|
|
42
|
+
|
|
43
|
+
### Cancellation
|
|
44
|
+
|
|
45
|
+
When a tool call is cancelled (MCP `notifications/cancelled`) or its job is cancelled with `qa_job_cancel`, the interrupted work returns `failureCode:"CANCELLED"` with `retrySafe:true`. A call's cancel signal applies to that call only: cancelling an interactive call does not cancel a running job, and the other way round.
|
|
46
|
+
|
|
47
|
+
`CANCELLED` is not a failure. It is never recorded as a tool error, a snapshot failure, a finding, or a health verdict, and it never switches the session to visual-fallback. Side effects that already happened are not rolled back: a cancelled `qa_act` always reports `changedState:true`, and a cancelled `qa_explore` stops with `stoppedReason:"cancelled"` and records no finding for the screen it was observing.
|
|
48
|
+
|
|
49
|
+
## Project root
|
|
50
|
+
|
|
51
|
+
Tools that need a project resolve it in this order; the first hit wins:
|
|
52
|
+
|
|
53
|
+
1. The `projectRoot` argument. It must be an absolute, existing directory. An invalid value is an error, never silently replaced.
|
|
54
|
+
2. MCP roots, when the client exposes a workspace. The first root with a project marker wins, else the first root that is not `/` or `$HOME`.
|
|
55
|
+
3. `SWIPIUM_PROJECT_ROOT` from the server environment.
|
|
56
|
+
4. `CLAUDE_PROJECT_DIR`, which Claude Code sets for stdio servers.
|
|
57
|
+
5. The server's working directory, but never `/` or `$HOME`, and only when it contains a project marker.
|
|
58
|
+
|
|
59
|
+
**Marker rule**: a directory is a project when it contains `package.json`, `app.json`, `pubspec.yaml`, `build.gradle(.kts)`, `settings.gradle(.kts)`, `Podfile`, an `.xcodeproj` or `.xcworkspace`, or an `android/` or `ios/` directory. Only the MCP-roots preference and the working-directory fallback check markers; the `projectRoot` argument and the two environment variables are trusted as given (absolute and existing).
|
|
60
|
+
|
|
61
|
+
When nothing resolves, tools fail with `PROJECT_ROOT_UNRESOLVED`: pass an absolute `projectRoot`, or set `SWIPIUM_PROJECT_ROOT` in the MCP server config's `env`. `qa_test_this` returns `PROJECT_ROOT_EMPTY` for an empty directory and `NOT_MOBILE_PROJECT` when no supported app is found there.
|
|
62
|
+
|
|
63
|
+
**`rootSource`**: results built from a freshly resolved root carry `projectRoot` and `rootSource` (`arg`, `mcp-roots`, `env:SWIPIUM_PROJECT_ROOT`, `env:CLAUDE_PROJECT_DIR`, or `cwd`). When the root came from the working directory, the text also says `project root taken from server cwd: <path>; pass projectRoot to override`. Calls that reuse a session's root do not re-resolve it and carry no `rootSource`.
|
|
64
|
+
|
|
65
|
+
## Consent
|
|
66
|
+
|
|
67
|
+
Privileged actions (build, boot, install, Metro start, data wipes, recordings, network changes, location spoofing, OCR, flow mutations, destructive exploration, remote WDA, writing into the project's test directory) are consent-gated. Nothing gated runs without approval.
|
|
68
|
+
|
|
69
|
+
### Mechanisms
|
|
70
|
+
|
|
71
|
+
- **Elicitation**: when the client supports MCP form elicitation, the server asks the user directly and the tool continues on approval. The model never sees a `consentId`. The prompt times out after 10 minutes.
|
|
72
|
+
- **Consent envelope** (client assertion): otherwise the tool returns `{requiresConsent:true, consentId, action, risk, explain, exactCommand, affects}`. The agent shows it to the user and, only after they agree, re-calls the same tool with the same arguments plus `consentId` and `approve:true`. Only `qa_test_this`'s envelope also carries `sessionId`, so its approving re-call reuses the session without `projectRoot`; for every other tool, re-call with the `sessionId` you already passed.
|
|
73
|
+
- **Policy**: with `SWIPIUM_REQUIRE_ELICITATION=1` in the server environment, a client that cannot elicit gets `CONSENT_REFUSED` for every gated action, before the model sees a `consentId`, so the envelope path cannot be used. The setting has no effect on clients that support elicitation.
|
|
74
|
+
|
|
75
|
+
How each action was approved (`elicitation`, `client-assertion`, or `policy`) is recorded in the report's [mutation ledger](#glossary).
|
|
76
|
+
|
|
77
|
+
### Outcomes
|
|
78
|
+
|
|
79
|
+
Nothing runs in any of these, and a `refused` row is written to the mutation ledger.
|
|
80
|
+
|
|
81
|
+
| Code | When | Retry-safe |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `CONSENT_DECLINED` | The user declined the prompt. | no |
|
|
84
|
+
| `CONSENT_CANCELLED` | The prompt was dismissed, timed out, failed in transport, or the call was aborted; also when the action changed while the user was deciding. Re-calling shows a fresh prompt. | yes |
|
|
85
|
+
| `CONSENT_REFUSED` | `SWIPIUM_REQUIRE_ELICITATION=1` is set and the client cannot elicit. | no |
|
|
86
|
+
|
|
87
|
+
Do not retry any of them without asking the user.
|
|
88
|
+
|
|
89
|
+
### Rules
|
|
90
|
+
|
|
91
|
+
- **Single use and binding**: a `consentId` works once, for the same action and targets it was issued for. A consent issued in a session works only in that session: an id from session A cannot approve a call in session B, and a replay from B does not use up A's consent. A mismatched action or target is refused without burning the id.
|
|
92
|
+
- **Lifetime**: a `consentId` expires 30 minutes after it is issued. At most 200 consents can be pending; beyond that the oldest is dropped.
|
|
93
|
+
- **Stale ids**: an unknown, used, or expired `consentId` is refused, never silently replaced. `qa_test_this` issues a new challenge and says so in `consentNote` (with `previousConsentId`).
|
|
94
|
+
- **Prompt sanitising**: the elicitation prompt quotes every repository-derived value (flow names, queries, URLs, commands) as JSON, strips control characters, newlines, line separators, zero-width and bidirectional characters, and caps each field (at most 4 command steps, 2000 characters overall), so repository content cannot fake extra prompt lines.
|
|
95
|
+
- **Unreviewed sources**: commands and URLs that come from the repository (seed commands, `.swipium/config.json` providers, a configured WDA URL) are labelled as repository-supplied and unreviewed.
|
|
96
|
+
|
|
97
|
+
### Consent actions and risk levels
|
|
98
|
+
|
|
99
|
+
| Action | Risk | Raised by |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `test_this_plan` | Highest of its steps: build high, external APK install medium, install from the project low, boot low | `qa_test_this` execute; `qa_test_feature` execute without a `sessionId` |
|
|
102
|
+
| `prepare_plan` | Medium for an external APK, else low | `qa_prepare_target` |
|
|
103
|
+
| `build_from_source` | High | `qa_build mode:"run"` |
|
|
104
|
+
| `install_app` | Medium (`qa_ios`, `qa_bundletool`); low or medium by path (`qa_prepare_ios_target`) | `qa_ios install`, `qa_bundletool install`, `qa_prepare_ios_target` |
|
|
105
|
+
| `erase_device` | High | `qa_ios erase` |
|
|
106
|
+
| `privacy_reset` | Low | `qa_ios privacy_reset` |
|
|
107
|
+
| `wda_build`, `wda_start` | Medium | `qa_wda` |
|
|
108
|
+
| `wda_non_loopback` | Medium | `qa_wda` with a remote URL |
|
|
109
|
+
| `app_clear_data`, `app_fresh_start` | High | `qa_app_control` |
|
|
110
|
+
| `start_metro` | Medium | `qa_metro start` |
|
|
111
|
+
| `network_change` | Medium | `qa_network`, `qa_mobile_audit` (resilience, release_gate) |
|
|
112
|
+
| `geo_set` | Medium | `qa_geolocation` |
|
|
113
|
+
| `screen_record` | Medium | `qa_screen_record start` |
|
|
114
|
+
| `ocr_run` | Medium | `qa_visual find_text` |
|
|
115
|
+
| `flow_mutation_run` | High for script seeds, else medium | `qa_flow_run` |
|
|
116
|
+
| `destructive_ui_candidate` | High | `qa_explore safeMode:"approved_destructive_candidate"` |
|
|
117
|
+
| `suite_fresh_state_replay` | Highest of its prepare and teardown steps | `qa_generate target:"suite" replay:"fresh_state"` |
|
|
118
|
+
| `automation_project_write` | Medium | `qa_generate target:"appium" integrateIntoProject:true` |
|
|
119
|
+
|
|
120
|
+
Booting a simulator with `qa_ios boot` is not gated (low-risk and reversible). Inside `qa_test_this` and `qa_prepare_target`, a boot is one step of the combined consent.
|
|
121
|
+
|
|
122
|
+
## Secrets and redaction
|
|
123
|
+
|
|
124
|
+
### What becomes a secret
|
|
125
|
+
|
|
126
|
+
- Text typed into a secure field (a password field, or a field whose label or id reads like password, OTP, PIN, CVV, card number, secret, token, or security code). This applies to native-selector typing too.
|
|
127
|
+
- Values answered through `qa_continue_from_blocker` whose field names match the credential-name rule below, plus any field listed in `secretFields` (which adds to the rule; it does not replace it).
|
|
128
|
+
- Resolved flow variables and `${SWIPIUM_*}` values from the environment whose names match the credential-name rule: a case-insensitive substring match on `pass`, `secret`, `token`, `otp`, `pin`, `cvv`, `key`, or `code` (so `SWIPIUM_VERIFICATION_CODE` is a secret).
|
|
129
|
+
- Fixture field values read from the environment (`fields.<name>.var`).
|
|
130
|
+
- A typed value that equals or contains a value already registered as a secret, even in an ordinary text field.
|
|
131
|
+
|
|
132
|
+
### How redaction works
|
|
133
|
+
|
|
134
|
+
- Registered secrets are scrubbed from tool text, structured results, persisted state, reports, text artifacts, OCR text, and provider `stderr`. JSON- and XML-escaped spellings are matched too.
|
|
135
|
+
- Values of 4 or more characters are matched anywhere. A 3-character value, or an all-digit value shorter than 8 characters, is matched only as a standalone token, so a PIN does not scrub unrelated numbers.
|
|
136
|
+
- **Values shorter than 3 characters are not scrubbed.** A text artifact written while such a secret was registered reports `redaction:"partial"` with a `redactionNote`; other artifacts report `applied`, and binary files `not-applied`.
|
|
137
|
+
- **Screenshots and recordings are pixels and are never redacted.** `qa_screenshot` and `qa_visual` withhold captures while a password or OTP field is on screen (`CAPTURE_WITHHELD_SECURE`, unless `force:true`).
|
|
138
|
+
- `qa_act type` never echoes the typed value. `redacted:true` and `secret:true` appear only when the value was treated as a secret; ordinary text omits both, and the recorded step keeps the text, so a generated suite contains it.
|
|
139
|
+
- **Placeholders in `qa_act`**: `${SWIPIUM_*}` names are expanded (only that prefix), from session inputs first, then the server environment. The value is typed but never echoed (`placeholders` lists the names), and the action is recorded with the placeholder. A typed literal that equals a stored session input is recorded as that input's placeholder. An unresolvable placeholder returns `MISSING_TEST_DATA` before anything is tapped.
|
|
140
|
+
|
|
141
|
+
### Environment access
|
|
142
|
+
|
|
143
|
+
Flows, fixture `fields.var`, and `qa_act` placeholders read the server environment only for `SWIPIUM_*` names. Other names are never read (flows fail the step with `MISSING_FIXTURE`; fixtures log a warning). Pass other values explicitly (`qa_flow_run variables`) or rename them. See [Flows: variables](flows.md#variables).
|
|
144
|
+
|
|
145
|
+
### Generated output
|
|
146
|
+
|
|
147
|
+
Every `qa_generate` target, `qa_suite_generate`, and the persisted state rewrite recorded secrets into environment placeholders at emit time, marked as needing human data. The placeholder is `SWIPIUM_TEST_PASSWORD`, `SWIPIUM_TEST_OTP`, `SWIPIUM_TEST_TOKEN`, or `SWIPIUM_TEST_PIN` by field kind, else `SWIPIUM_SECRET_<n>`. An existing `${VAR}` is kept, prefixed with `SWIPIUM_` when needed.
|
|
148
|
+
|
|
149
|
+
Form data that `qa_explore` generates is recorded under `SWIPIUM_TEST_EMAIL`, `SWIPIUM_TEST_PASSWORD`, or `SWIPIUM_TEST_OTP`, else `SWIPIUM_GEN_<FIELD>` (a username is `SWIPIUM_GEN_USERNAME`). `qa_first_run` uses the same names, except that a username is `SWIPIUM_TEST_USERNAME`.
|
|
150
|
+
|
|
151
|
+
Registered secret values never reach `test-suite.json`, `TC-*.yaml`, or `state.json`. A final guard scans the output against the session's registered values (not only name heuristics); if one would still be written, generation fails with `SECRET_IN_GENERATED_OUTPUT` and nothing is written.
|
|
152
|
+
|
|
153
|
+
### Sensitive sessions
|
|
154
|
+
|
|
155
|
+
`qa_start_session {sensitive:true}` refuses every screenshot, recording, log capture, and other on-screen evidence (`SENSITIVE_MODE_REFUSED`). Sensitive sessions are never listed as MCP resources.
|
|
156
|
+
|
|
157
|
+
## iOS modes
|
|
158
|
+
|
|
159
|
+
Swipium drives iOS Simulators in one of two modes.
|
|
160
|
+
|
|
161
|
+
| Mode | When | What works |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| **Structured (WDA)** | WebDriverAgent is reachable and attached. | `qa_snapshot`, `qa_act` by `@eN` ref, text, id, or native selector (`accessibility id`, `name`, `predicate string`, `class chain`), structured flows. |
|
|
164
|
+
| **Visual-only** | No reachable WDA. | `qa_screenshot`, `qa_visual` (assert, baseline, diff, OCR, image find), coordinate taps through `idb` when it is installed, flows in `mode: visual` or `auto`. `qa_snapshot`, `qa_act`, and structured flows return `BACKEND_UNSUPPORTED`. |
|
|
165
|
+
|
|
166
|
+
`qa_test_this` and `qa_prepare_ios_target` (`attachWda:"auto"`) fall back to visual-only automatically, with a recorded workaround, unless WDA was explicitly required. `qa_orientation`, `qa_geolocation`, and `qa_network` are not available on iOS in either mode.
|
|
167
|
+
|
|
168
|
+
- **Artifacts**: only simulator `.app` bundles install. A `.ipa` targets a real device and is refused with `IPA_NEEDS_REAL_DEVICE`.
|
|
169
|
+
- **Session capabilities**: every WDA session created for an app sends `shouldTerminateApp:false` (unless `ios.wda.capabilities` in `.swipium/config.json` sets it), because WDA tears down the previous session with that session's setting. Re-binding a resumed session after a restart, and recovering from an invalid-session error, also send `forceAppLaunch:false`, so the running app is reused; the latter adds a warning to verify the screen state.
|
|
170
|
+
- **Managed WDA lifetime**: `qa_wda start` spawns WDA (`xcodebuild test-without-building`) and records it in `~/.swipium/processes.json`; `qa_wda stop` terminates it, including one adopted from a previous server run. A normal server shutdown does not stop managed WDA, so a resumed iOS session can keep using it.
|
|
171
|
+
- **Adoption at startup**: a managed WDA from a previous run is adopted only when it is less than 12 hours old (from its original start) and `GET /status` reports ready; older or unhealthy ones are stopped, as are orphaned Metro bundlers and screen recorders. Orphaned emulators are adopted and left running. A process owned by another running Swipium server is never touched. Each registered process records its start time and full command line, and an orphan is signalled or adopted only when both still match, so a recycled pid is never touched. The startup sweep runs in the background after the server connects.
|
|
172
|
+
- **Remote WDA rule**: loopback means `localhost`, `127.0.0.0/8`, or `[::1]`. A non-loopback `webDriverAgentUrl` needs `allowNonLoopback:true` plus the `wda_non_loopback` consent, otherwise `DESTRUCTIVE_REFUSED`. The only pre-approval is user-level: `SWIPIUM_ALLOW_REMOTE_WDA`, a comma-separated list of exact WDA base URLs in the MCP server's environment. The repository's `.swipium/config.json` cannot pre-approve one: `ios.wda.allowNonLoopbackUrls` only adds a note, and a non-loopback `ios.wda.url` is labelled "configured by the repository (.swipium/config.json), unreviewed" in the prompt. `qa_prepare_ios_target` and `qa_test_this` never connect to a non-loopback configured URL on their own.
|
|
173
|
+
|
|
174
|
+
## Devices
|
|
175
|
+
|
|
176
|
+
Swipium drives Android Emulators and iOS Simulators on the local machine. Physical devices are out of scope ([physical-devices.md](physical-devices.md)).
|
|
177
|
+
|
|
178
|
+
### Physical devices
|
|
179
|
+
|
|
180
|
+
A connected phone is refused with `PHYSICAL_DEVICE_UNSUPPORTED` only when:
|
|
181
|
+
|
|
182
|
+
- it is requested explicitly (its serial or UDID as `device`),
|
|
183
|
+
- it is the only option (no emulator online, no AVD to boot, and it is the only device on the chosen platform), or
|
|
184
|
+
- `preferRealDevice` is set and a phone is visible.
|
|
185
|
+
|
|
186
|
+
Otherwise target selection (`qa_resolve_target`, `qa_test_this`) picks an emulator or simulator and only mentions the phone in the selection `reason` (for example `Physical device <serial> is visible but out of scope`). Two exceptions: `qa_test_this` refuses `preferRealDevice:true` even when no phone is visible, and refuses an artifact that installs only on a real iOS device; and `qa_prepare_target` acts on the online device rather than planning: a phone that is the only online device is refused, and with more than one device online (a phone included) and no `device` it returns `MULTIPLE_DEVICES`, so pass the emulator's serial. `qa_test_this` is the path that boots an AVD while a phone is connected.
|
|
187
|
+
|
|
188
|
+
### Android
|
|
189
|
+
|
|
190
|
+
- **Emulator detection**: an `emulator-NNNN` serial is an emulator. Any other serial, such as an adb-over-TCP `localhost:5555` or `127.0.0.1:<port>`, counts as an emulator only when one `getprop` probe shows an emulator (`ro.kernel.qemu=1`, `ro.boot.qemu=1`, a goldfish or ranchu `ro.hardware`, or Genymotion). When a session binds a device whose properties cannot be read, it is refused as `DEVICE_NOT_READY`; if they show real hardware, `PHYSICAL_DEVICE_UNSUPPORTED`.
|
|
191
|
+
- **Booting**: when an AVD must be booted (headless by default), Swipium records the serials online before the boot and uses only a new serial that is a verified emulator, then waits for `sys.boot_completed` (up to 180 s). So when a phone is connected and an AVD exists, it boots the AVD and installs only on the new emulator. A boot that never comes up is `EMULATOR_BOOT_FAILED`; an online device that never finishes booting is `DEVICE_NOT_READY`.
|
|
192
|
+
- **Several devices**: with more than one device online (phones count) and no `device` argument, `qa_prepare_target` returns `MULTIPLE_DEVICES` with the online serials.
|
|
193
|
+
- **Binding**: a session is only re-bound to its own device. An offline device is never replaced by a different online one.
|
|
194
|
+
- **Toolchain**: a missing `adb` is `ADB_NOT_FOUND` from `qa_test_this`. `qa_network` needs Android 11+.
|
|
195
|
+
|
|
196
|
+
### iOS Simulator
|
|
197
|
+
|
|
198
|
+
See [iOS modes](#ios-modes). `qa_ios` and `qa_prepare_ios_target` pick a simulator by UDID or name substring; `qa_wda attach` refuses to guess (`MULTIPLE_DEVICES`) when no device is given and none is bound to the session.
|
|
199
|
+
|
|
200
|
+
## Glossary
|
|
201
|
+
|
|
202
|
+
| Term | Meaning |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| **`@eN` ref** | A handle such as `@e3` for one element of the latest `qa_snapshot`. Refs are invalid after navigation; an old one returns `STALE_REF`. |
|
|
205
|
+
| **App map** | Swipium's durable memory of the app in `.swipium/app-map.json`: features, screens, navigation, tests, and a code index, each with provenance. Built by `qa_app_map_build` and updated by runs. |
|
|
206
|
+
| **Approval mechanism** | How a consent was decided: `elicitation` (a real user prompt), `client-assertion` (the client re-called with `consentId` and `approve:true`), or `policy` (refused by `SWIPIUM_REQUIRE_ELICITATION=1`). Recorded in the mutation ledger. |
|
|
207
|
+
| **Budget** | A session's limits on time, actions, screenshots, consecutive snapshot failures, and no-change actions. A spent budget returns `{ok:true, stopped:true, reason}`. |
|
|
208
|
+
| **Canonical suite** | The durable, curated test-case catalog in `.swipium/test-suite.json`, managed by the `qa_suite_*` tools. Unlike per-run assets, it grows across runs. |
|
|
209
|
+
| **Capability group** | One of the groups the tools are organised into (start, setup, build, device, drive, run, app-map, feature, flows, generate, test-suite, issues, first-run). `qa_status` without a session returns them. |
|
|
210
|
+
| **Consent** | A user approval for one privileged action. See [Consent](#consent). |
|
|
211
|
+
| **Failure bucket** | How to triage a failure code: `app_bug`, `environment`, `missing_data`, `mcp_limitation`, or `unsafe_refused`. |
|
|
212
|
+
| **Failure owner** | Who fixes a failure: `app`, `environment`, `swipium`, or `user`. Independent of the bucket; `qa_explain_blocker` returns both. |
|
|
213
|
+
| **Finding** | A deterministic health observation (crash, ANR, error boundary, wrong foreground app, …) recorded in the session and report. |
|
|
214
|
+
| **Fixture** | A declared precondition (test account, saved record, disposable data), from `qa_start_session` or `.swipium/fixtures.json`. Unmet fixtures make a workflow blocked instead of failed. |
|
|
215
|
+
| **Flow** | A replayable YAML script of steps under `.swipium/flows/`. See [Flows](flows.md). |
|
|
216
|
+
| **Flow V2** | The current flow format: selector-bound input, visual steps, device-relative gestures, waits, setup and teardown, and `structured`, `visual`, or `auto` mode. |
|
|
217
|
+
| **Job** | A background operation with a `jobId`, polled with `qa_job_status`. See [Jobs](#jobs). |
|
|
218
|
+
| **Mutation ledger** | The report's audit trail of every state-changing action: tool, action, risk, target, consent (with its approval mechanism), and status (`requested`, `approved`, `executed`, `refused`, `blocked`, or `restored`). |
|
|
219
|
+
| **Pack** | A YAML list of flows run together, under `.swipium/packs/`. |
|
|
220
|
+
| **POM suite** | A generated page-object-model suite (page objects plus suites under `.swipium/`) that compiles into runnable flows. Produced by `qa_generate target:"suite"`. |
|
|
221
|
+
| **Response mode** | `compact`, `normal`, or `verbose`: how much of a result is repeated in the text channel. See [Response modes](tools.md#response-modes). |
|
|
222
|
+
| **Sensitive session** | A session started with `sensitive:true`; it refuses all pixel, video, and log capture. |
|
|
223
|
+
| **Stale client** | A client still running a pre-upgrade tool list; removed calls return `STALE_CLIENT`. |
|
|
224
|
+
| **Visual-fallback** | A per-screen switch to screenshot-based work after repeated failed UI-tree dumps (`VISUAL_ONLY_SCREEN`). The next successful structured observation switches back. |
|
|
225
|
+
| **Visual-only** | An iOS session without WDA. See [iOS modes](#ios-modes). |
|
|
226
|
+
| **WDA** | WebDriverAgent, the on-simulator automation server Appium uses. It gives iOS a UI tree. |
|
|
227
|
+
| **Workaround** | A safe fallback Swipium chose on its own (for example visual-only iOS), listed in the report. |
|