swipium 1.5.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -0
- package/README.md +173 -280
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +30 -27
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +47 -21
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +2 -2
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +3 -3
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +2 -2
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +2 -2
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/issues.js +10 -10
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +5 -5
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +77 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +2 -2
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +3 -3
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +1 -1
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +17 -17
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +55 -7
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +3 -3
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +5 -5
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +10 -10
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +3 -3
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +1 -1
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +7 -7
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +3 -3
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +14 -25
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +5 -5
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +2 -2
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +13 -7
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +5 -5
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +265 -106
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +71 -26
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +276 -105
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +6 -4
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +20 -8
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +2 -2
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +7 -7
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +35 -14
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +6 -6
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +45 -14
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +6 -6
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +3 -3
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +65 -62
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +300 -36
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +120 -7
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +350 -50
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +2 -2
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +5 -5
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/policy.js +5 -5
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +301 -284
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +1 -1
- package/dist/featureTesting/executionBootstrap.js +29 -30
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +2 -2
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +12 -12
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +7 -7
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +10 -10
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +6 -6
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +4 -4
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +1 -1
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +1 -1
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +11 -11
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +3 -3
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +5 -5
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +11 -11
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +8 -8
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +9 -9
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +5 -5
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +33 -3
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +20 -10
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/pack.js +3 -3
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +111 -23
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +153 -57
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +50 -5
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +4 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/issues/classify.js +16 -16
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +24 -4
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +94 -31
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +4 -4
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +4 -4
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +4 -4
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +75 -7
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +32 -7
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +1 -1
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +370 -31
- package/dist/lib/lockfile.js.map +1 -1
- package/dist/lib/needsInput.js +3 -3
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +2 -2
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +79 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +3 -3
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +3 -2
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +52 -10
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +294 -15
- package/dist/lib/wda.js.map +1 -1
- package/dist/mobileAudit/checks.js +12 -12
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +1 -1
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +3 -3
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +72 -18
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +219 -36
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +63 -17
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +13 -13
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +4 -4
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +62 -21
- package/dist/orchestration/testThis/execute.js.map +1 -1
- package/dist/orchestration/testThis/pipeline.js +119 -30
- package/dist/orchestration/testThis/pipeline.js.map +1 -1
- package/dist/orchestration/testThis/plan.js +162 -73
- package/dist/orchestration/testThis/plan.js.map +1 -1
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +34 -16
- package/dist/orchestration/testThis/terminal.js.map +1 -1
- package/dist/orchestration/testThis/types.js +1 -1
- package/dist/orchestration/testThis/types.js.map +1 -1
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +22 -22
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/export.js +392 -37
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/policy.js +7 -4
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +8 -8
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +472 -45
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +2 -2
- package/dist/services/build.js.map +1 -1
- package/dist/{tools → services}/flowGenerate.js +29 -13
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +4 -4
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +46 -8
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +24 -9
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +100 -45
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +32 -16
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +28 -3
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +40 -27
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +270 -6
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +317 -48
- package/dist/session/processRegistry.js.map +1 -1
- package/dist/session/progress.js +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +275 -48
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +53 -8
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +28 -4
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +11 -5
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +46 -2
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +9 -5
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +4 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +50 -41
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +9 -7
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +4 -4
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +13 -9
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +5 -5
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +5 -5
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +3 -3
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +794 -164
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +451 -128
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +69 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +188 -130
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +32 -42
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +21 -31
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +55 -12
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +76 -24
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +68 -28
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +41 -47
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +31 -48
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +34 -41
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +183 -103
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +25 -13
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +52 -89
- package/dist/tools/generate.js.map +1 -1
- package/dist/tools/getArtifact.js +16 -5
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +15 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +45 -223
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +251 -35
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +41 -16
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +36 -35
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +50 -45
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +31 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +26 -26
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +10 -16
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +117 -50
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +11 -22
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +11 -15
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +78 -24
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +23 -21
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +23 -16
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +20 -16
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +155 -118
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +104 -116
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +72 -43
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +30 -20
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +24 -28
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +686 -210
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -38
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +187 -57
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +31 -22
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +53 -6
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +80 -12
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +194 -128
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +812 -152
- package/package.json +3 -2
- package/dist/tools/assertVisual.js +0 -84
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -179
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -86
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/locator.js +0 -85
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/permissions.js +0 -174
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -52
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -76
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -133
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -217
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -106
- package/dist/tools/visualText.js.map +0 -1
package/dist/report/export.js
CHANGED
|
@@ -1,9 +1,39 @@
|
|
|
1
|
-
// Report 2.0 exporters
|
|
1
|
+
// Report 2.0 exporters. Turn the assembled report into formats a developer
|
|
2
2
|
// can use without the agent transcript: an issue-ready Markdown doc and a CI-ready JUnit XML.
|
|
3
|
-
// (a third-party-format exporter is deferred
|
|
3
|
+
// (a third-party-format exporter is deferred and out of scope here.)
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Callers pass the report through redactReportData() FIRST (B9): every string field (steps,
|
|
6
|
+
// nextStep, missingPrecondition, workflow names, …) is scrubbed with the session redactor before
|
|
7
|
+
// any format escapes it, so XML/JSON/Markdown escaping can never hide a secret from redaction.
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
6
9
|
import { evidenceTaxonomyForNotes } from './evidence.js';
|
|
10
|
+
import { FAILURES } from '../oracle/failures.js';
|
|
11
|
+
import { scrubVolatile } from '../issues/fingerprint.js';
|
|
12
|
+
import { uniqueKey } from '../lib/redact.js';
|
|
13
|
+
/**
|
|
14
|
+
* Deep-redact a report (B9): returns a copy where EVERY string value at any depth (and every string
|
|
15
|
+
* object key) has been passed through the session redactor. Run it once on the assembled report,
|
|
16
|
+
* before persisting it or rendering any export format. Escaping (`&` > `&`, `"` > `\"`) would
|
|
17
|
+
* otherwise turn a secret like `P@ss&w0rd"1` into text the redactor no longer matches.
|
|
18
|
+
*/
|
|
19
|
+
export function redactReportData(value, redact) {
|
|
20
|
+
const walk = (v) => {
|
|
21
|
+
if (typeof v === 'string')
|
|
22
|
+
return redact(v) ?? v;
|
|
23
|
+
if (Array.isArray(v))
|
|
24
|
+
return v.map(walk);
|
|
25
|
+
if (v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype) {
|
|
26
|
+
const out = {};
|
|
27
|
+
// Two keys that redact to the same spelling (e.g. "hunter22" and a literal "«redacted»")
|
|
28
|
+
// are disambiguated with a `#2` suffix instead of one silently overwriting the other.
|
|
29
|
+
for (const [k, val] of Object.entries(v))
|
|
30
|
+
out[uniqueKey(out, redact(k) ?? k)] = walk(val);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
34
|
+
};
|
|
35
|
+
return walk(value);
|
|
36
|
+
}
|
|
7
37
|
function playwrightStatus(outcome) {
|
|
8
38
|
if (outcome === 'pass')
|
|
9
39
|
return 'passed';
|
|
@@ -201,7 +231,7 @@ function tableCell(s) {
|
|
|
201
231
|
}
|
|
202
232
|
function compactValue(value, maxLength = 180) {
|
|
203
233
|
if (value == null)
|
|
204
|
-
return '
|
|
234
|
+
return '-';
|
|
205
235
|
let raw;
|
|
206
236
|
try {
|
|
207
237
|
raw = typeof value === 'string' ? value : JSON.stringify(value);
|
|
@@ -210,11 +240,11 @@ function compactValue(value, maxLength = 180) {
|
|
|
210
240
|
raw = String(value);
|
|
211
241
|
}
|
|
212
242
|
if (!raw)
|
|
213
|
-
return '
|
|
243
|
+
return '-';
|
|
214
244
|
return raw.length > maxLength ? `${raw.slice(0, Math.max(0, maxLength - 1))}…` : raw;
|
|
215
245
|
}
|
|
216
246
|
function mutationTime(at) {
|
|
217
|
-
return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '
|
|
247
|
+
return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '-';
|
|
218
248
|
}
|
|
219
249
|
function mutationConsentSummary(m) {
|
|
220
250
|
if (!m.consent)
|
|
@@ -229,7 +259,7 @@ function mutationEvidenceSummary(m) {
|
|
|
229
259
|
m.ledgerUri ? `[ledger](${m.ledgerUri})` : '',
|
|
230
260
|
m.consent?.payloadHash ? `payload ${m.consent.payloadHash.slice(0, 12)}` : '',
|
|
231
261
|
].filter(Boolean);
|
|
232
|
-
return parts.length ? parts.join('; ') : '
|
|
262
|
+
return parts.length ? parts.join('; ') : '-';
|
|
233
263
|
}
|
|
234
264
|
export function inlinePrSummary(r) {
|
|
235
265
|
const problem = firstReportProblem(r);
|
|
@@ -245,7 +275,7 @@ export function inlinePrSummary(r) {
|
|
|
245
275
|
if (r.automationBackend)
|
|
246
276
|
lines.push(`Backend: ${r.automationBackend.description}.`);
|
|
247
277
|
if (r.readiness?.length)
|
|
248
|
-
lines.push(`Readiness: ${r.readiness.join('
|
|
278
|
+
lines.push(`Readiness: ${r.readiness.join(' > ')}.`);
|
|
249
279
|
if (r.automationReadiness)
|
|
250
280
|
lines.push(`Automation readiness: ${r.automationReadiness.grade} (${r.automationReadiness.score}/100).`);
|
|
251
281
|
lines.push(`Likely category: ${category}.`, `Evidence: ${evidenceSummary(r, problem)}.`, `Next action: ${r.executiveSummary.nextAction}`);
|
|
@@ -254,7 +284,7 @@ export function inlinePrSummary(r) {
|
|
|
254
284
|
export function toMarkdown(r) {
|
|
255
285
|
const L = [];
|
|
256
286
|
const evidenceTaxonomy = r.evidenceTaxonomy ?? evidenceTaxonomyForNotes(r.testOutcomes);
|
|
257
|
-
L.push(`# QA report
|
|
287
|
+
L.push(`# QA report: ${r.appId ?? 'app'}`);
|
|
258
288
|
L.push('');
|
|
259
289
|
L.push('## PR summary', '');
|
|
260
290
|
L.push('```text');
|
|
@@ -278,7 +308,7 @@ export function toMarkdown(r) {
|
|
|
278
308
|
for (const reason of r.executiveSummary.reasons)
|
|
279
309
|
L.push(`- ${reason}`);
|
|
280
310
|
}
|
|
281
|
-
// Issue memory
|
|
311
|
+
// Issue memory: durable, cross-run issue ledger summary + recurrence warnings.
|
|
282
312
|
if (r.issuesMarkdown) {
|
|
283
313
|
L.push('');
|
|
284
314
|
L.push(r.issuesMarkdown);
|
|
@@ -290,7 +320,7 @@ export function toMarkdown(r) {
|
|
|
290
320
|
}
|
|
291
321
|
if (r.readiness?.length) {
|
|
292
322
|
L.push('', '## Capability readiness', '');
|
|
293
|
-
L.push(`- Labels: ${r.readiness.join('
|
|
323
|
+
L.push(`- Labels: ${r.readiness.join(' > ')}.`);
|
|
294
324
|
L.push(`- Highest: ${r.readiness.at(-1)}.`);
|
|
295
325
|
}
|
|
296
326
|
if (r.wda) {
|
|
@@ -350,7 +380,7 @@ export function toMarkdown(r) {
|
|
|
350
380
|
.filter(Boolean)
|
|
351
381
|
.join('; ')
|
|
352
382
|
.replace(/\|/g, '\\|');
|
|
353
|
-
L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '
|
|
383
|
+
L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '-'} |`);
|
|
354
384
|
}
|
|
355
385
|
L.push('', '## Evidence quality', '');
|
|
356
386
|
L.push(`- Deterministic structured locator: ${evidenceTaxonomy.counts.structured_locator}.`);
|
|
@@ -360,7 +390,7 @@ export function toMarkdown(r) {
|
|
|
360
390
|
L.push('', '| Workflow | Evidence | Authority | Notes |');
|
|
361
391
|
L.push('| --- | --- | --- | --- |');
|
|
362
392
|
for (const ev of evidenceTaxonomy.assessments) {
|
|
363
|
-
L.push(`| ${tableCell(ev.workflow)} | ${ev.kind} | ${ev.authority} | ${ev.warning ? tableCell(ev.warning) : '
|
|
393
|
+
L.push(`| ${tableCell(ev.workflow)} | ${ev.kind} | ${ev.authority} | ${ev.warning ? tableCell(ev.warning) : '-'} |`);
|
|
364
394
|
}
|
|
365
395
|
}
|
|
366
396
|
if (r.flakeClassification && r.flakeClassification.repeat > 1) {
|
|
@@ -376,7 +406,7 @@ export function toMarkdown(r) {
|
|
|
376
406
|
L.push('| Fixture | Field | Generator | Variable | Value | Evidence |');
|
|
377
407
|
L.push('| --- | --- | --- | --- | --- | --- |');
|
|
378
408
|
for (const g of r.generatedValues) {
|
|
379
|
-
L.push(`| ${tableCell(g.fixture)} | ${tableCell(g.field)} | ${tableCell(g.generator)} | \`${tableCell(g.varName)}\` | ${g.secret ? '<redacted>' : tableCell(g.value)} | ${g.artifactUri ? `[artifact](${g.artifactUri})` : '
|
|
409
|
+
L.push(`| ${tableCell(g.fixture)} | ${tableCell(g.field)} | ${tableCell(g.generator)} | \`${tableCell(g.varName)}\` | ${g.secret ? '<redacted>' : tableCell(g.value)} | ${g.artifactUri ? `[artifact](${g.artifactUri})` : '-'} |`);
|
|
380
410
|
}
|
|
381
411
|
}
|
|
382
412
|
if (r.findings.length) {
|
|
@@ -386,7 +416,7 @@ export function toMarkdown(r) {
|
|
|
386
416
|
const bucket = f.bucket ? ` ${f.bucket}` : '';
|
|
387
417
|
const retry = f.retrySafe == null ? '' : ` retrySafe=${f.retrySafe}`;
|
|
388
418
|
const next = f.nextStep ? ` Next: ${f.nextStep}` : '';
|
|
389
|
-
L.push(`- **[${f.severity}]**${code}${bucket}${retry} ${f.layer ?? '?'}/${f.kind}: ${f.detail}${f.
|
|
419
|
+
L.push(`- **[${f.severity}]**${code}${bucket}${retry} ${f.layer ?? '?'}/${f.kind}: ${f.detail}${f.count && f.count > 1 ? ` (×${f.count})` : ''}${f.evidence ? `: _"${f.evidence}"_` : ''}${f.screenshotUri ? ` ([screenshot](${f.screenshotUri}))` : ''}${next}`);
|
|
390
420
|
}
|
|
391
421
|
}
|
|
392
422
|
if (r.environmentChanges.length || r.guardrailOverrides.length) {
|
|
@@ -407,10 +437,14 @@ export function toMarkdown(r) {
|
|
|
407
437
|
L.push('| Time | Tool | Action | Risk | Status | Consent | Evidence | Target / detail |');
|
|
408
438
|
L.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
|
|
409
439
|
for (const m of r.mutationLedger) {
|
|
410
|
-
const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : ''].filter((part) => part && part !== '
|
|
440
|
+
const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : ''].filter((part) => part && part !== '-').join('; ') || '-';
|
|
411
441
|
L.push(`| ${tableCell(mutationTime(m.at))} | ${tableCell(m.tool)} | ${tableCell(m.action)} | ${tableCell(m.risk)} | ${tableCell(m.status)} | ${tableCell(mutationConsentSummary(m))} | ${tableCell(mutationEvidenceSummary(m))} | ${tableCell(targetDetail)} |`);
|
|
412
442
|
}
|
|
413
443
|
}
|
|
444
|
+
if (r.redactionDegraded) {
|
|
445
|
+
L.push('', '## Redaction notice', '');
|
|
446
|
+
L.push('- ⚠️ This session was restored after a server restart. Secrets registered before the restart are no longer in the redaction set, so artifacts written after the restart may contain those values unredacted.');
|
|
447
|
+
}
|
|
414
448
|
L.push('', '## Timing & artifacts', '');
|
|
415
449
|
const fmtS = (ms) => `${Math.round((ms ?? 0) / 1000)}s`;
|
|
416
450
|
L.push(`- Total ${fmtS(r.phaseTimings.totalMs)} · setup ${fmtS(r.phaseTimings.setupMs)} · active ${fmtS(r.phaseTimings.activeMs)}${r.phaseTimings.timeToLoginMs != null ? ` · to-login ${fmtS(r.phaseTimings.timeToLoginMs)}` : ''}`);
|
|
@@ -444,35 +478,356 @@ export function toMarkdown(r) {
|
|
|
444
478
|
L.push('', '---', '_Generated by Swipium._');
|
|
445
479
|
return L.join('\n');
|
|
446
480
|
}
|
|
447
|
-
|
|
448
|
-
|
|
481
|
+
/** Characters XML 1.0 forbids even as character references (https://www.w3.org/TR/xml/#charsets):
|
|
482
|
+
* C0 controls except TAB/LF/CR, lone UTF-16 surrogates, and U+FFFE/U+FFFF. Logcat/Metro output
|
|
483
|
+
* routinely carries ANSI escapes (\x1b) and NULs; one such byte makes CI reporters reject the whole
|
|
484
|
+
* JUnit file, so each is replaced with U+FFFD (visible, and legal). */
|
|
485
|
+
const XML_ILLEGAL =
|
|
486
|
+
// eslint-disable-next-line no-control-regex
|
|
487
|
+
/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
488
|
+
export function stripXmlIllegal(s) {
|
|
489
|
+
return s.replace(XML_ILLEGAL, '\uFFFD');
|
|
490
|
+
}
|
|
491
|
+
/** Escape a string for XML text or attribute position. Newlines are entity-escaped too, so a
|
|
492
|
+
* multi-line failure reason survives attribute position and round-trips through any XML parser. */
|
|
493
|
+
export function escapeXml(s) {
|
|
494
|
+
return stripXmlIllegal(s)
|
|
495
|
+
.replace(/&/g, '&')
|
|
496
|
+
.replace(/</g, '<')
|
|
497
|
+
.replace(/>/g, '>')
|
|
498
|
+
.replace(/"/g, '"')
|
|
499
|
+
.replace(/\r/g, ' ')
|
|
500
|
+
.replace(/\n/g, ' ');
|
|
501
|
+
}
|
|
502
|
+
/** Map the report's failures onto policy FlowVerdicts (src/report/policy.ts): every recorded
|
|
503
|
+
* workflow outcome plus every high-severity finding. This is the seam that lets the release-gate
|
|
504
|
+
* policy (.swipium/policy.json blockOn/warnOn/ignoreKnown) decide the CI pass/fail verdict. */
|
|
505
|
+
export function reportPolicyVerdicts(r) {
|
|
506
|
+
const verdicts = r.testOutcomes.map((n) => ({
|
|
507
|
+
flow: n.workflow,
|
|
508
|
+
passed: n.outcome !== 'fail',
|
|
509
|
+
failureCode: n.outcome === 'fail' ? workflowFailureCode(n) : undefined,
|
|
510
|
+
}));
|
|
511
|
+
for (const f of r.findings) {
|
|
512
|
+
if (f.severity === 'high')
|
|
513
|
+
verdicts.push({ flow: `finding:${f.kind}`, passed: false, failureCode: f.failureCode ?? f.kind });
|
|
514
|
+
}
|
|
515
|
+
return verdicts;
|
|
516
|
+
}
|
|
517
|
+
function junitTestcase(lines, name, classname, body, systemOut) {
|
|
518
|
+
if (!body.length && !systemOut.length) {
|
|
519
|
+
lines.push(` <testcase name="${escapeXml(name)}" classname="${escapeXml(classname)}"></testcase>`);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
lines.push(` <testcase name="${escapeXml(name)}" classname="${escapeXml(classname)}">`);
|
|
523
|
+
lines.push(...body);
|
|
524
|
+
if (systemOut.length)
|
|
525
|
+
lines.push(` <system-out>${escapeXml(systemOut.join('\n'))}</system-out>`);
|
|
526
|
+
lines.push(` </testcase>`);
|
|
527
|
+
}
|
|
528
|
+
/** The policy key reportPolicyVerdicts/applyPolicy use for a failing workflow / high finding
|
|
529
|
+
* (`${flow}: ${code}`), so exporters can tell which failures the release gate did NOT block. */
|
|
530
|
+
function workflowFailureCode(n) {
|
|
531
|
+
return n.steps?.find((s) => s.failureCode)?.failureCode ?? n.category ?? 'UNKNOWN';
|
|
532
|
+
}
|
|
533
|
+
function workflowPolicyKey(n) {
|
|
534
|
+
return `${n.workflow}: ${workflowFailureCode(n)}`;
|
|
449
535
|
}
|
|
450
|
-
|
|
451
|
-
|
|
536
|
+
function findingPolicyKey(f) {
|
|
537
|
+
return `finding:${f.kind}: ${f.failureCode ?? f.kind}`;
|
|
538
|
+
}
|
|
539
|
+
/** Failures the gate let through (warnOn / ignoreKnown), keyed like PolicyDecision entries. */
|
|
540
|
+
function nonBlockingFailures(policy) {
|
|
541
|
+
const out = new Map();
|
|
542
|
+
for (const k of policy?.warned ?? [])
|
|
543
|
+
out.set(k, 'warned');
|
|
544
|
+
for (const k of policy?.suppressed ?? [])
|
|
545
|
+
out.set(k, 'suppressed');
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
/** JUnit XML for CI test-report sinks. Two suites: recorded workflow outcomes (fail > <failure>;
|
|
549
|
+
* blocked/skipped/not_applicable > <skipped>) and findings (high > <failure>, medium/low pass with
|
|
550
|
+
* the detail in system-out). Evidence URIs are listed in each testcase's system-out. When a
|
|
551
|
+
* release-gate PolicyDecision is provided, it is recorded as testsuite <properties> AND it shapes
|
|
552
|
+
* the verdicts (D5): a failure the policy only warns on or ignores (warnOn / ignoreKnown) is
|
|
553
|
+
* emitted as <skipped message="policy …"> rather than <failure>, so "gate PASS" never fails CI. */
|
|
554
|
+
export function toJUnit(r, policy) {
|
|
452
555
|
const cases = r.testOutcomes;
|
|
453
|
-
const
|
|
454
|
-
const
|
|
556
|
+
const lenient = nonBlockingFailures(policy);
|
|
557
|
+
const caseVerdict = (n) => {
|
|
558
|
+
if (n.outcome === 'pass')
|
|
559
|
+
return 'pass';
|
|
560
|
+
if (n.outcome !== 'fail')
|
|
561
|
+
return 'skipped';
|
|
562
|
+
return lenient.has(workflowPolicyKey(n)) ? 'skipped' : 'failure';
|
|
563
|
+
};
|
|
564
|
+
const findingVerdict = (f) => f.severity !== 'high' ? 'pass' : lenient.has(findingPolicyKey(f)) ? 'skipped' : 'failure';
|
|
565
|
+
const failures = cases.filter((n) => caseVerdict(n) === 'failure').length;
|
|
566
|
+
const skipped = cases.filter((n) => caseVerdict(n) === 'skipped').length;
|
|
567
|
+
const findingFailures = r.findings.filter((f) => findingVerdict(f) === 'failure').length;
|
|
568
|
+
const findingSkipped = r.findings.filter((f) => findingVerdict(f) === 'skipped').length;
|
|
455
569
|
const suiteName = `swipium.${r.appId ?? 'app'}`;
|
|
570
|
+
const timeSec = (r.phaseTimings.totalMs ?? 0) / 1000;
|
|
456
571
|
const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
|
|
457
|
-
lines.push(`<
|
|
572
|
+
lines.push(`<testsuites name="${escapeXml(suiteName)}" tests="${cases.length + r.findings.length}" failures="${failures + findingFailures}" skipped="${skipped + findingSkipped}" time="${timeSec}">`);
|
|
573
|
+
lines.push(` <testsuite name="${escapeXml(suiteName)}" tests="${cases.length}" failures="${failures}" skipped="${skipped}" time="${timeSec}">`);
|
|
574
|
+
if (policy) {
|
|
575
|
+
lines.push(' <properties>');
|
|
576
|
+
lines.push(` <property name="swipium.releaseGate" value="${policy.block ? 'fail' : 'pass'}"></property>`);
|
|
577
|
+
lines.push(` <property name="swipium.releaseGate.reason" value="${escapeXml(policy.reason)}"></property>`);
|
|
578
|
+
lines.push(' </properties>');
|
|
579
|
+
}
|
|
458
580
|
for (const n of cases) {
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
581
|
+
const text = [n.category, n.reason, n.missingPrecondition].filter(Boolean).join(': ') || n.outcome;
|
|
582
|
+
const body = [];
|
|
583
|
+
const verdict = caseVerdict(n);
|
|
584
|
+
if (verdict === 'failure')
|
|
585
|
+
body.push(` <failure message="${escapeXml(text)}">${escapeXml(text)}</failure>`);
|
|
586
|
+
else if (verdict === 'skipped') {
|
|
587
|
+
const why = n.outcome === 'fail' ? `policy ${lenient.get(workflowPolicyKey(n))} (release gate not blocked): ${text}` : text;
|
|
588
|
+
body.push(` <skipped message="${escapeXml(why)}"></skipped>`);
|
|
465
589
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
590
|
+
junitTestcase(lines, n.workflow, suiteName, body, n.artifactUris ?? []);
|
|
591
|
+
}
|
|
592
|
+
lines.push(' </testsuite>');
|
|
593
|
+
if (r.findings.length) {
|
|
594
|
+
const findingsSuite = `${suiteName}.findings`;
|
|
595
|
+
lines.push(` <testsuite name="${escapeXml(findingsSuite)}" tests="${r.findings.length}" failures="${findingFailures}" skipped="${findingSkipped}" time="0">`);
|
|
596
|
+
for (const f of r.findings) {
|
|
597
|
+
const name = `[${f.severity}] ${f.layer ?? '?'}/${f.kind}`;
|
|
598
|
+
const detail = `${f.detail}${f.evidence ? `: "${f.evidence}"` : ''}${f.nextStep ? ` Next: ${f.nextStep}` : ''}`;
|
|
599
|
+
const body = [];
|
|
600
|
+
const systemOut = f.screenshotUri ? [f.screenshotUri] : [];
|
|
601
|
+
const verdict = findingVerdict(f);
|
|
602
|
+
if (verdict === 'failure')
|
|
603
|
+
body.push(` <failure message="${escapeXml(f.failureCode ?? f.kind)}">${escapeXml(detail)}</failure>`);
|
|
604
|
+
else if (verdict === 'skipped') {
|
|
605
|
+
body.push(` <skipped message="${escapeXml(`policy ${lenient.get(findingPolicyKey(f))} (release gate not blocked): ${f.failureCode ?? f.kind}`)}"></skipped>`);
|
|
606
|
+
systemOut.unshift(detail);
|
|
607
|
+
}
|
|
608
|
+
else
|
|
609
|
+
systemOut.unshift(detail);
|
|
610
|
+
junitTestcase(lines, name, findingsSuite, body, systemOut);
|
|
473
611
|
}
|
|
612
|
+
lines.push(' </testsuite>');
|
|
474
613
|
}
|
|
475
|
-
lines.push('</
|
|
614
|
+
lines.push('</testsuites>');
|
|
476
615
|
return lines.join('\n');
|
|
477
616
|
}
|
|
617
|
+
function sarifLevel(severity) {
|
|
618
|
+
return severity === 'high' ? 'error' : severity === 'medium' ? 'warning' : 'note';
|
|
619
|
+
}
|
|
620
|
+
/** Stable 16-hex digest (volatile ids/timestamps scrubbed) for SARIF partialFingerprints. */
|
|
621
|
+
function stableHash(...parts) {
|
|
622
|
+
return createHash('sha256')
|
|
623
|
+
.update(parts.map((p) => scrubVolatile(p.toLowerCase())).join('|'))
|
|
624
|
+
.digest('hex')
|
|
625
|
+
.slice(0, 16);
|
|
626
|
+
}
|
|
627
|
+
/** SARIF 2.1.0 for code-scanning sinks (GitHub `upload-sarif`). Findings > results with
|
|
628
|
+
* ruleId = failureCode/kind and level from severity (high > error, medium > warning, low > note); failed
|
|
629
|
+
* workflows are results too. Per GitHub's SARIF requirements
|
|
630
|
+
* (https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning):
|
|
631
|
+
* - every result has locations[0] > a REAL repo-relative file (`uriBaseId: %SRCROOT%`, full
|
|
632
|
+
* region at line 1): the app-map source of the screen/workflow when known, else the project
|
|
633
|
+
* manifest (`options.sources`, built by resolveSarifSources). Results without one are dropped
|
|
634
|
+
* by code scanning ("at least one location is required").
|
|
635
|
+
* - partialFingerprints carry a stable per-result hash (own `primaryLocationLineHash`, so results
|
|
636
|
+
* anchored to the same manifest line don't collapse into one alert) plus `swipium/v1`.
|
|
637
|
+
* - rules carry short/full descriptions + help from the failure catalog.
|
|
638
|
+
* swipium:// evidence URIs stay in relatedLocations / properties. invocations[0].executionSuccessful
|
|
639
|
+
* means "the tool ran" (always true here); the release-gate verdict lives in run.properties. */
|
|
640
|
+
export function toSarif(r, policy, options = {}) {
|
|
641
|
+
const sources = options.sources ?? { defaultUri: 'package.json' };
|
|
642
|
+
const anchor = (file) => ({
|
|
643
|
+
physicalLocation: {
|
|
644
|
+
artifactLocation: { uri: file, uriBaseId: '%SRCROOT%' },
|
|
645
|
+
region: { startLine: 1, startColumn: 1, endLine: 1, endColumn: 2 },
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
const evidenceLocations = (uris) => uris.length
|
|
649
|
+
? {
|
|
650
|
+
relatedLocations: uris.map((uri, i) => ({
|
|
651
|
+
id: i + 1,
|
|
652
|
+
physicalLocation: { artifactLocation: { uri } },
|
|
653
|
+
message: { text: 'Swipium evidence artifact (resolve with qa_get_artifact)' },
|
|
654
|
+
})),
|
|
655
|
+
}
|
|
656
|
+
: {};
|
|
657
|
+
const seen = new Map();
|
|
658
|
+
const fingerprints = (ruleId, ...identity) => {
|
|
659
|
+
const base = stableHash(ruleId, ...identity);
|
|
660
|
+
// Two genuinely identical results in one run still need distinct alerts-per-occurrence ids.
|
|
661
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
662
|
+
seen.set(base, n);
|
|
663
|
+
return { primaryLocationLineHash: `${base}:${n}`, 'swipium/v1': `${base}:${n}` };
|
|
664
|
+
};
|
|
665
|
+
const results = r.findings.map((f) => {
|
|
666
|
+
const ruleId = f.failureCode ?? f.kind;
|
|
667
|
+
const file = (f.screen && sources.byScreen?.[f.screen.toLowerCase()]) || sources.defaultUri;
|
|
668
|
+
return {
|
|
669
|
+
ruleId,
|
|
670
|
+
level: sarifLevel(f.severity),
|
|
671
|
+
message: { text: `${f.detail}${f.evidence ? `: "${f.evidence}"` : ''}${f.nextStep ? ` Next: ${f.nextStep}` : ''}` },
|
|
672
|
+
locations: [anchor(file)],
|
|
673
|
+
partialFingerprints: fingerprints(ruleId, f.kind, f.screen ?? '', f.detail),
|
|
674
|
+
...evidenceLocations(f.screenshotUri ? [f.screenshotUri] : []),
|
|
675
|
+
properties: {
|
|
676
|
+
severity: f.severity,
|
|
677
|
+
...(f.layer ? { layer: f.layer } : {}),
|
|
678
|
+
...(f.bucket ? { bucket: f.bucket } : {}),
|
|
679
|
+
...(f.screen ? { screen: f.screen } : {}),
|
|
680
|
+
...(f.retrySafe == null ? {} : { retrySafe: f.retrySafe }),
|
|
681
|
+
...(f.screenshotUri ? { evidenceUris: [f.screenshotUri] } : {}),
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
});
|
|
685
|
+
for (const n of r.testOutcomes) {
|
|
686
|
+
if (n.outcome !== 'fail')
|
|
687
|
+
continue;
|
|
688
|
+
const ruleId = n.steps?.find((s) => s.failureCode)?.failureCode ?? n.category ?? 'WORKFLOW_FAILED';
|
|
689
|
+
const file = sources.byWorkflow?.[n.workflow.toLowerCase()] ?? sources.defaultUri;
|
|
690
|
+
results.push({
|
|
691
|
+
ruleId,
|
|
692
|
+
level: 'error',
|
|
693
|
+
message: { text: `${n.workflow}: ${[n.reason, n.missingPrecondition].filter(Boolean).join('; ') || 'workflow failed'}` },
|
|
694
|
+
locations: [anchor(file)],
|
|
695
|
+
partialFingerprints: fingerprints(ruleId, 'workflow', n.workflow),
|
|
696
|
+
...evidenceLocations(n.artifactUris ?? []),
|
|
697
|
+
properties: {
|
|
698
|
+
workflow: n.workflow,
|
|
699
|
+
outcome: n.outcome,
|
|
700
|
+
...(n.category ? { category: n.category } : {}),
|
|
701
|
+
...(n.artifactUris?.length ? { evidenceUris: n.artifactUris } : {}),
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const catalog = FAILURES;
|
|
706
|
+
const rules = [...new Set(results.map((res) => res.ruleId))].map((id) => {
|
|
707
|
+
const info = catalog[id];
|
|
708
|
+
const short = info?.summary ?? `Swipium ${id.replace(/_/g, ' ').toLowerCase()}`;
|
|
709
|
+
const full = info ? `${info.summary}. Bucket: ${info.bucket}.` : `Swipium mobile QA result "${id}" observed on a device run.`;
|
|
710
|
+
const help = info?.recovery ?? 'Open the linked Swipium evidence (screenshots, logs) for this run and reproduce on a device.';
|
|
711
|
+
return {
|
|
712
|
+
id,
|
|
713
|
+
name: id,
|
|
714
|
+
shortDescription: { text: short.slice(0, 1024) },
|
|
715
|
+
fullDescription: { text: full.slice(0, 1024) },
|
|
716
|
+
help: { text: help, markdown: help },
|
|
717
|
+
properties: { tags: ['swipium', 'mobile-qa', ...(info?.bucket ? [info.bucket] : [])] },
|
|
718
|
+
};
|
|
719
|
+
});
|
|
720
|
+
const sarif = {
|
|
721
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
722
|
+
version: '2.1.0',
|
|
723
|
+
runs: [
|
|
724
|
+
{
|
|
725
|
+
tool: { driver: { name: 'swipium', informationUri: 'https://github.com/GeroPalombo/swipium', rules } },
|
|
726
|
+
automationDetails: { id: `swipium/${r.appId ?? 'app'}/` },
|
|
727
|
+
originalUriBaseIds: {
|
|
728
|
+
'%SRCROOT%': { description: { text: 'The project root Swipium ran against (repository checkout root in CI).' } },
|
|
729
|
+
},
|
|
730
|
+
invocations: [{ executionSuccessful: true }],
|
|
731
|
+
results,
|
|
732
|
+
properties: {
|
|
733
|
+
sessionId: r.sessionId,
|
|
734
|
+
appId: r.appId,
|
|
735
|
+
device: r.device,
|
|
736
|
+
releaseRisk: r.executiveSummary.risk,
|
|
737
|
+
nextAction: r.executiveSummary.nextAction,
|
|
738
|
+
releaseGateVerdict: policy ? (policy.block ? 'block' : 'pass') : r.executiveSummary.risk === 'block' ? 'block' : 'pass',
|
|
739
|
+
...(policy ? { releaseGate: policy } : {}),
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
};
|
|
744
|
+
return JSON.stringify(sarif, null, 2);
|
|
745
|
+
}
|
|
746
|
+
/** Longest free-text cell in the job summary; one giant log dump must not eat the 1 MiB budget. */
|
|
747
|
+
const SUMMARY_CELL_MAX = 2000;
|
|
748
|
+
function summaryCell(s) {
|
|
749
|
+
const clipped = s.length > SUMMARY_CELL_MAX ? `${s.slice(0, SUMMARY_CELL_MAX - 1)}…` : s;
|
|
750
|
+
return tableCell(mdText(clipped)).replace(/\r?\n/g, '<br>');
|
|
751
|
+
}
|
|
752
|
+
/** Neutralize Markdown/HTML in free text shown in the job summary (app ids, device names, policy
|
|
753
|
+
* reasons, next actions come from the device / project and are not trusted Markdown). */
|
|
754
|
+
export function mdText(s) {
|
|
755
|
+
return s
|
|
756
|
+
.replace(/&/g, '&')
|
|
757
|
+
.replace(/</g, '<')
|
|
758
|
+
.replace(/>/g, '>')
|
|
759
|
+
.replace(/([\\`*_[\]#|~])/g, '\\$1');
|
|
760
|
+
}
|
|
761
|
+
/** Inline-code span that survives backticks/newlines in the value. */
|
|
762
|
+
function mdCode(s) {
|
|
763
|
+
const flat = s.replace(/\r?\n/g, ' ');
|
|
764
|
+
return flat.includes('`') ? `\`\` ${flat.replace(/``+/g, '`')} \`\`` : `\`${flat}\``;
|
|
765
|
+
}
|
|
766
|
+
/** GitHub caps a step summary at 1 MiB (larger uploads are rejected with an error annotation). We
|
|
767
|
+
* stop at ~900 KB so the verdict survives, and say so. */
|
|
768
|
+
export const GITHUB_SUMMARY_MAX_BYTES = 900 * 1024;
|
|
769
|
+
/** Concise GitHub Actions job-summary Markdown (write it to $GITHUB_STEP_SUMMARY): verdict,
|
|
770
|
+
* counts by severity, top findings table, workflow outcomes, and evidence links. Capped at
|
|
771
|
+
* GITHUB_SUMMARY_MAX_BYTES with a truncation note. */
|
|
772
|
+
export function toGithubSummary(r, policy) {
|
|
773
|
+
const L = [];
|
|
774
|
+
L.push(`## Swipium QA: ${RISK_BADGE[r.executiveSummary.risk] ?? mdText(r.executiveSummary.risk.toUpperCase())}`);
|
|
775
|
+
L.push('');
|
|
776
|
+
L.push(`App ${mdCode(r.appId ?? 'unknown')} on ${mdCode(r.device ?? 'device')}, session ${mdCode(r.sessionId)}.`);
|
|
777
|
+
L.push('');
|
|
778
|
+
if (policy)
|
|
779
|
+
L.push(`**Release gate (policy):** ${policy.block ? '❌ FAIL' : '✅ PASS'}: ${mdText(policy.reason)}`, '');
|
|
780
|
+
L.push(`**Next action:** ${mdText(r.executiveSummary.nextAction)}`);
|
|
781
|
+
L.push('');
|
|
782
|
+
const sevCount = (sev) => r.findings.filter((f) => f.severity === sev).length;
|
|
783
|
+
const outcomes = Object.entries(r.outcomeTally)
|
|
784
|
+
.map(([k, v]) => `${OUTCOME_MARK[k] ?? ''} ${mdText(k)}=${v}`)
|
|
785
|
+
.join(' · ');
|
|
786
|
+
L.push(`Findings: **${sevCount('high')} high** · ${sevCount('medium')} medium · ${sevCount('low')} low. Workflows: ${outcomes || 'none recorded'}.`);
|
|
787
|
+
if (r.findings.length) {
|
|
788
|
+
L.push('', '### Top findings', '', '| Severity | Code | Detail | Evidence |', '| --- | --- | --- | --- |');
|
|
789
|
+
const bySeverity = { high: 0, medium: 1, low: 2 };
|
|
790
|
+
const top = [...r.findings].sort((a, b) => (bySeverity[a.severity] ?? 3) - (bySeverity[b.severity] ?? 3)).slice(0, 10);
|
|
791
|
+
for (const f of top) {
|
|
792
|
+
const evidence = f.screenshotUri ? `[screenshot](${f.screenshotUri})` : '-';
|
|
793
|
+
const times = f.count && f.count > 1 ? ` (×${f.count})` : '';
|
|
794
|
+
L.push(`| ${summaryCell(f.severity)} | ${tableCell(mdCode(f.failureCode ?? f.kind))} | ${summaryCell(f.detail)}${times} | ${evidence} |`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
const problems = r.testOutcomes.filter((n) => n.outcome !== 'pass');
|
|
798
|
+
if (problems.length) {
|
|
799
|
+
L.push('', '### Failed / blocked workflows', '', '| Workflow | Outcome | Reason | Evidence |', '| --- | --- | --- | --- |');
|
|
800
|
+
for (const n of problems) {
|
|
801
|
+
const reason = [n.category, n.reason, n.missingPrecondition].filter(Boolean).join(': ') || '-';
|
|
802
|
+
const evidence = (n.artifactUris ?? []).map((uri, i) => `[${i + 1}](${uri})`).join(' ') || '-';
|
|
803
|
+
L.push(`| ${summaryCell(n.workflow)} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${summaryCell(reason)} | ${evidence} |`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const evidenceArtifacts = r.artifacts.filter((a) => ['screenshot', 'recording', 'report'].includes(a.kind)).slice(0, 10);
|
|
807
|
+
if (evidenceArtifacts.length) {
|
|
808
|
+
L.push('', '### Evidence', '');
|
|
809
|
+
for (const a of evidenceArtifacts)
|
|
810
|
+
L.push(`- ${mdCode(a.uri)} (${mdText(a.kind)}${a.label ? `: ${mdText(a.label)}` : ''})`);
|
|
811
|
+
}
|
|
812
|
+
L.push('', '_Generated by Swipium._');
|
|
813
|
+
return capSummary(L);
|
|
814
|
+
}
|
|
815
|
+
/** Join lines, dropping trailing ones (whole lines, never mid-table-row) past the byte cap. */
|
|
816
|
+
function capSummary(lines) {
|
|
817
|
+
const full = lines.join('\n');
|
|
818
|
+
if (Buffer.byteLength(full, 'utf8') <= GITHUB_SUMMARY_MAX_BYTES)
|
|
819
|
+
return full;
|
|
820
|
+
const note = `\n\n> ⚠️ Summary truncated at ${Math.round(GITHUB_SUMMARY_MAX_BYTES / 1024)} KB (GitHub's step-summary limit is 1 MiB). See the JUnit/SARIF/Markdown exports for the full report.`;
|
|
821
|
+
const budget = GITHUB_SUMMARY_MAX_BYTES - Buffer.byteLength(note, 'utf8');
|
|
822
|
+
const kept = [];
|
|
823
|
+
let used = 0;
|
|
824
|
+
for (const line of lines) {
|
|
825
|
+
const size = Buffer.byteLength(line, 'utf8') + 1;
|
|
826
|
+
if (used + size > budget)
|
|
827
|
+
break;
|
|
828
|
+
kept.push(line);
|
|
829
|
+
used += size;
|
|
830
|
+
}
|
|
831
|
+
return kept.join('\n') + note;
|
|
832
|
+
}
|
|
478
833
|
//# sourceMappingURL=export.js.map
|