swipium 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +313 -0
- package/README.md +175 -219
- package/THREAT_MODEL.md +223 -47
- package/dist/appMap/automationLink.js +39 -26
- package/dist/appMap/automationLink.js.map +1 -1
- package/dist/appMap/build.js +68 -20
- package/dist/appMap/build.js.map +1 -1
- package/dist/appMap/codeIndex.js +31 -5
- package/dist/appMap/codeIndex.js.map +1 -1
- package/dist/appMap/featureIndex.js +23 -5
- package/dist/appMap/featureIndex.js.map +1 -1
- package/dist/appMap/featureModel.js +44 -8
- package/dist/appMap/featureModel.js.map +1 -1
- package/dist/appMap/firstRunApply.js +24 -6
- package/dist/appMap/firstRunApply.js.map +1 -1
- package/dist/appMap/fsWalk.js +19 -2
- package/dist/appMap/fsWalk.js.map +1 -1
- package/dist/appMap/issues.js +10 -30
- package/dist/appMap/issues.js.map +1 -1
- package/dist/appMap/migrations.js +6 -6
- package/dist/appMap/migrations.js.map +1 -1
- package/dist/appMap/prelaunch.js +2 -2
- package/dist/appMap/prelaunch.js.map +1 -1
- package/dist/appMap/projectRegistry.js +83 -11
- package/dist/appMap/projectRegistry.js.map +1 -1
- package/dist/appMap/provenance.js +2 -2
- package/dist/appMap/provenance.js.map +1 -1
- package/dist/appMap/query.js +21 -5
- package/dist/appMap/query.js.map +1 -1
- package/dist/appMap/runtimeMerge.js +12 -5
- package/dist/appMap/runtimeMerge.js.map +1 -1
- package/dist/appMap/schema.js +1 -1
- package/dist/appMap/schema.js.map +1 -1
- package/dist/appMap/screenMatch.js +6 -2
- package/dist/appMap/screenMatch.js.map +1 -1
- package/dist/appMap/staticScan.js +151 -39
- package/dist/appMap/staticScan.js.map +1 -1
- package/dist/appMap/store.js +58 -85
- package/dist/appMap/store.js.map +1 -1
- package/dist/appMap/tsAstScan.js +34 -12
- package/dist/appMap/tsAstScan.js.map +1 -1
- package/dist/artifacts/bundletool.js +49 -12
- package/dist/artifacts/bundletool.js.map +1 -1
- package/dist/artifacts/resolve.js +41 -14
- package/dist/artifacts/resolve.js.map +1 -1
- package/dist/automation/capabilities.js +112 -30
- package/dist/automation/capabilities.js.map +1 -1
- package/dist/automation/gestures.js +6 -2
- package/dist/automation/gestures.js.map +1 -1
- package/dist/automation/plan.js +71 -23
- package/dist/automation/plan.js.map +1 -1
- package/dist/automation/report.js +15 -5
- package/dist/automation/report.js.map +1 -1
- package/dist/automation/selectors.js +22 -28
- package/dist/automation/selectors.js.map +1 -1
- package/dist/automation/types.js +1 -1
- package/dist/automation/types.js.map +1 -1
- package/dist/automation/waits.js +14 -8
- package/dist/automation/waits.js.map +1 -1
- package/dist/automation/webview.js +9 -3
- package/dist/automation/webview.js.map +1 -1
- package/dist/automationGen/appiumModel.js +16 -8
- package/dist/automationGen/appiumModel.js.map +1 -1
- package/dist/automationGen/ciEmitter.js +6 -6
- package/dist/automationGen/ciEmitter.js.map +1 -1
- package/dist/automationGen/identifiers.js +224 -0
- package/dist/automationGen/identifiers.js.map +1 -0
- package/dist/automationGen/jsEmitter.js +288 -121
- package/dist/automationGen/jsEmitter.js.map +1 -1
- package/dist/automationGen/packagePatch.js +2 -2
- package/dist/automationGen/packagePatch.js.map +1 -1
- package/dist/automationGen/platformResolve.js +36 -0
- package/dist/automationGen/platformResolve.js.map +1 -0
- package/dist/automationGen/projectProfile.js +76 -27
- package/dist/automationGen/projectProfile.js.map +1 -1
- package/dist/automationGen/pythonEmitter.js +278 -107
- package/dist/automationGen/pythonEmitter.js.map +1 -1
- package/dist/automationGen/readmeEmitter.js +7 -6
- package/dist/automationGen/readmeEmitter.js.map +1 -1
- package/dist/automationGen/run.js +388 -0
- package/dist/automationGen/run.js.map +1 -0
- package/dist/automationGen/suitePlan.js +17 -10
- package/dist/automationGen/suitePlan.js.map +1 -1
- package/dist/automationGen/validation.js +52 -12
- package/dist/automationGen/validation.js.map +1 -1
- package/dist/build/parseBuildLog.js +235 -31
- package/dist/build/parseBuildLog.js.map +1 -1
- package/dist/build/plan.js +39 -18
- package/dist/build/plan.js.map +1 -1
- package/dist/ci/preflight.js +13 -4
- package/dist/ci/preflight.js.map +1 -1
- package/dist/cli/gc.js +74 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/init.js +217 -26
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +50 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/report.js +218 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.js +36 -15
- package/dist/cli/scan.js.map +1 -1
- package/dist/cli/suite.js +7 -7
- package/dist/cli/suite.js.map +1 -1
- package/dist/cli/verify.js +7 -7
- package/dist/cli/verify.js.map +1 -1
- package/dist/consent/consent.js +162 -5
- package/dist/consent/consent.js.map +1 -1
- package/dist/context/detect.js +55 -18
- package/dist/context/detect.js.map +1 -1
- package/dist/context/findApps.js +22 -8
- package/dist/context/findApps.js.map +1 -1
- package/dist/context/projectRoot.js +174 -19
- package/dist/context/projectRoot.js.map +1 -1
- package/dist/context/scan.js +1 -1
- package/dist/context/scan.js.map +1 -1
- package/dist/core/capabilityGroups.js +90 -0
- package/dist/core/capabilityGroups.js.map +1 -0
- package/dist/core/target.js +10 -8
- package/dist/core/target.js.map +1 -1
- package/dist/core/targetPlan.js +160 -39
- package/dist/core/targetPlan.js.map +1 -1
- package/dist/drivers/DirectDriver.js +317 -43
- package/dist/drivers/DirectDriver.js.map +1 -1
- package/dist/drivers/SimctlDriver.js +124 -8
- package/dist/drivers/SimctlDriver.js.map +1 -1
- package/dist/drivers/WdaDriver.js +365 -55
- package/dist/drivers/WdaDriver.js.map +1 -1
- package/dist/explore/candidates.js +7 -3
- package/dist/explore/candidates.js.map +1 -1
- package/dist/explore/graph.js +15 -6
- package/dist/explore/graph.js.map +1 -1
- package/dist/explore/memory.js +4 -1
- package/dist/explore/memory.js.map +1 -1
- package/dist/explore/planner.js +54 -9
- package/dist/explore/planner.js.map +1 -1
- package/dist/explore/policy.js +11 -6
- package/dist/explore/policy.js.map +1 -1
- package/dist/explore/runner.js +363 -190
- package/dist/explore/runner.js.map +1 -1
- package/dist/explore/signatures.js +4 -2
- package/dist/explore/signatures.js.map +1 -1
- package/dist/explore/suite.js +3 -1
- package/dist/explore/suite.js.map +1 -1
- package/dist/featureTesting/executionBootstrap.js +137 -29
- package/dist/featureTesting/executionBootstrap.js.map +1 -1
- package/dist/featureTesting/featureMap.js +6 -3
- package/dist/featureTesting/featureMap.js.map +1 -1
- package/dist/featureTesting/featureScope.js +101 -20
- package/dist/featureTesting/featureScope.js.map +1 -1
- package/dist/featureTesting/mapFeatureScope.js +44 -15
- package/dist/featureTesting/mapFeatureScope.js.map +1 -1
- package/dist/featureTesting/objectiveModel.js +15 -11
- package/dist/featureTesting/objectiveModel.js.map +1 -1
- package/dist/featureTesting/resultMerge.js +16 -8
- package/dist/featureTesting/resultMerge.js.map +1 -1
- package/dist/featureTesting/sources.js +8 -5
- package/dist/featureTesting/sources.js.map +1 -1
- package/dist/featureTesting/suiteBridge.js +8 -2
- package/dist/featureTesting/suiteBridge.js.map +1 -1
- package/dist/featureTesting/synonyms.js +32 -4
- package/dist/featureTesting/synonyms.js.map +1 -1
- package/dist/featureTesting/testCaseFactory.js +27 -15
- package/dist/featureTesting/testCaseFactory.js.map +1 -1
- package/dist/featureTesting/testPlan.js +34 -12
- package/dist/featureTesting/testPlan.js.map +1 -1
- package/dist/firstRun/authStateMachine.js +1 -1
- package/dist/firstRun/authStateMachine.js.map +1 -1
- package/dist/firstRun/classifyScreen.js +9 -6
- package/dist/firstRun/classifyScreen.js.map +1 -1
- package/dist/firstRun/firstRunPlanner.js +16 -13
- package/dist/firstRun/firstRunPlanner.js.map +1 -1
- package/dist/firstRun/firstRunRunner.js +92 -31
- package/dist/firstRun/firstRunRunner.js.map +1 -1
- package/dist/firstRun/generatedDataPolicy.js +25 -15
- package/dist/firstRun/generatedDataPolicy.js.map +1 -1
- package/dist/firstRun/inputPlanner.js +45 -11
- package/dist/firstRun/inputPlanner.js.map +1 -1
- package/dist/firstRun/onboardingStateMachine.js +1 -1
- package/dist/firstRun/onboardingStateMachine.js.map +1 -1
- package/dist/firstRun/paywallPolicy.js +3 -3
- package/dist/firstRun/paywallPolicy.js.map +1 -1
- package/dist/firstRun/types.js +1 -1
- package/dist/firstRun/types.js.map +1 -1
- package/dist/fixtures/catalog.js +72 -9
- package/dist/fixtures/catalog.js.map +1 -1
- package/dist/fixtures/load.js +50 -0
- package/dist/fixtures/load.js.map +1 -0
- package/dist/flows/discover.js +2 -2
- package/dist/flows/discover.js.map +1 -1
- package/dist/flows/generate.js +68 -17
- package/dist/flows/generate.js.map +1 -1
- package/dist/flows/lint.js +88 -14
- package/dist/flows/lint.js.map +1 -1
- package/dist/flows/pack.js +21 -5
- package/dist/flows/pack.js.map +1 -1
- package/dist/flows/paths.js +57 -0
- package/dist/flows/paths.js.map +1 -0
- package/dist/flows/repair.js +176 -37
- package/dist/flows/repair.js.map +1 -1
- package/dist/flows/run.js +308 -99
- package/dist/flows/run.js.map +1 -1
- package/dist/flows/schema.js +107 -25
- package/dist/flows/schema.js.map +1 -1
- package/dist/flows/seedExec.js +8 -4
- package/dist/flows/seedExec.js.map +1 -1
- package/dist/flows/templates.js +5 -5
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/ios/signing.js +32 -3
- package/dist/ios/signing.js.map +1 -1
- package/dist/issues/classify.js +28 -20
- package/dist/issues/classify.js.map +1 -1
- package/dist/issues/fingerprint.js +35 -9
- package/dist/issues/fingerprint.js.map +1 -1
- package/dist/issues/index.js +93 -46
- package/dist/issues/index.js.map +1 -1
- package/dist/issues/metrics.js +6 -5
- package/dist/issues/metrics.js.map +1 -1
- package/dist/issues/recurrence.js +35 -8
- package/dist/issues/recurrence.js.map +1 -1
- package/dist/issues/report.js +12 -8
- package/dist/issues/report.js.map +1 -1
- package/dist/issues/reportBridge.js +40 -9
- package/dist/issues/reportBridge.js.map +1 -1
- package/dist/issues/schema.js +2 -2
- package/dist/issues/schema.js.map +1 -1
- package/dist/issues/sourceRevision.js +2 -2
- package/dist/issues/sourceRevision.js.map +1 -1
- package/dist/issues/store.js +110 -32
- package/dist/issues/store.js.map +1 -1
- package/dist/lib/abortScope.js +48 -0
- package/dist/lib/abortScope.js.map +1 -0
- package/dist/lib/android.js +84 -8
- package/dist/lib/android.js.map +1 -1
- package/dist/lib/coordSpace.js +1 -1
- package/dist/lib/coordSpace.js.map +1 -1
- package/dist/lib/device.js +53 -12
- package/dist/lib/device.js.map +1 -1
- package/dist/lib/gestures.js +141 -0
- package/dist/lib/gestures.js.map +1 -0
- package/dist/lib/gitignore.js +4 -3
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/image.js +12 -3
- package/dist/lib/image.js.map +1 -1
- package/dist/lib/lockfile.js +399 -0
- package/dist/lib/lockfile.js.map +1 -0
- package/dist/lib/needsInput.js +9 -6
- package/dist/lib/needsInput.js.map +1 -1
- package/dist/lib/png.js +4 -3
- package/dist/lib/png.js.map +1 -1
- package/dist/lib/redact.js +170 -9
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/result.js +94 -10
- package/dist/lib/result.js.map +1 -1
- package/dist/lib/schemaHash.js +7 -5
- package/dist/lib/schemaHash.js.map +1 -1
- package/dist/lib/sensitive.js +6 -3
- package/dist/lib/sensitive.js.map +1 -1
- package/dist/lib/simctl.js +3 -3
- package/dist/lib/simctl.js.map +1 -1
- package/dist/lib/spawn.js +78 -17
- package/dist/lib/spawn.js.map +1 -1
- package/dist/lib/toolAnnotations.js +114 -0
- package/dist/lib/toolAnnotations.js.map +1 -0
- package/dist/lib/wda.js +321 -25
- package/dist/lib/wda.js.map +1 -1
- package/dist/lib/wdaConfig.js +12 -5
- package/dist/lib/wdaConfig.js.map +1 -1
- package/dist/lib/wdaTune.js +52 -23
- package/dist/lib/wdaTune.js.map +1 -1
- package/dist/mobileAudit/checks.js +150 -28
- package/dist/mobileAudit/checks.js.map +1 -1
- package/dist/mobileAudit/evidence.js +4 -4
- package/dist/mobileAudit/evidence.js.map +1 -1
- package/dist/mobileAudit/profiles.js +121 -20
- package/dist/mobileAudit/profiles.js.map +1 -1
- package/dist/mobileAudit/results.js +9 -4
- package/dist/mobileAudit/results.js.map +1 -1
- package/dist/mobileAudit/runner.js +214 -35
- package/dist/mobileAudit/runner.js.map +1 -1
- package/dist/oracle/auth.js +1 -1
- package/dist/oracle/auth.js.map +1 -1
- package/dist/oracle/failures.js +1085 -145
- package/dist/oracle/failures.js.map +1 -1
- package/dist/oracle/health.js +148 -26
- package/dist/oracle/health.js.map +1 -1
- package/dist/oracle/locator.js +148 -19
- package/dist/oracle/locator.js.map +1 -1
- package/dist/oracle/record.js +3 -3
- package/dist/oracle/record.js.map +1 -1
- package/dist/orchestration/envelope.js +2 -2
- package/dist/orchestration/envelope.js.map +1 -1
- package/dist/orchestration/goal.js +7 -13
- package/dist/orchestration/goal.js.map +1 -1
- package/dist/orchestration/testThis/execute.js +167 -0
- package/dist/orchestration/testThis/execute.js.map +1 -0
- package/dist/orchestration/testThis/pipeline.js +413 -0
- package/dist/orchestration/testThis/pipeline.js.map +1 -0
- package/dist/orchestration/testThis/plan.js +519 -0
- package/dist/orchestration/testThis/plan.js.map +1 -0
- package/dist/orchestration/testThis/sessionIntent.js +72 -0
- package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
- package/dist/orchestration/testThis/terminal.js +136 -0
- package/dist/orchestration/testThis/terminal.js.map +1 -0
- package/dist/orchestration/testThis/types.js +12 -0
- package/dist/orchestration/testThis/types.js.map +1 -0
- package/dist/plan/plan.js +9 -9
- package/dist/plan/plan.js.map +1 -1
- package/dist/prompts/index.js +24 -24
- package/dist/prompts/index.js.map +1 -1
- package/dist/report/coverage.js +0 -1
- package/dist/report/coverage.js.map +1 -1
- package/dist/report/evidence.js.map +1 -1
- package/dist/report/export.js +449 -72
- package/dist/report/export.js.map +1 -1
- package/dist/report/findingsDedupe.js +41 -0
- package/dist/report/findingsDedupe.js.map +1 -0
- package/dist/report/flake.js +13 -14
- package/dist/report/flake.js.map +1 -1
- package/dist/report/history.js +72 -38
- package/dist/report/history.js.map +1 -1
- package/dist/report/policy.js +18 -7
- package/dist/report/policy.js.map +1 -1
- package/dist/report/qaLevel.js +10 -16
- package/dist/report/qaLevel.js.map +1 -1
- package/dist/report/readiness.js +42 -12
- package/dist/report/readiness.js.map +1 -1
- package/dist/report/sarifSources.js +160 -0
- package/dist/report/sarifSources.js.map +1 -0
- package/dist/report/summary.js +2 -2
- package/dist/report/summary.js.map +1 -1
- package/dist/report/testCatalog.js +4 -4
- package/dist/report/testCatalog.js.map +1 -1
- package/dist/report/timing.js +23 -23
- package/dist/report/timing.js.map +1 -1
- package/dist/report/toolHealth.js +124 -0
- package/dist/report/toolHealth.js.map +1 -0
- package/dist/server.js +501 -69
- package/dist/server.js.map +1 -1
- package/dist/services/automationGenerate.js +8 -2
- package/dist/services/automationGenerate.js.map +1 -1
- package/dist/services/build.js +10 -4
- package/dist/services/build.js.map +1 -1
- package/dist/services/flowGenerate.js +75 -0
- package/dist/services/flowGenerate.js.map +1 -0
- package/dist/services/preflight.js +40 -8
- package/dist/services/preflight.js.map +1 -1
- package/dist/services/prepareAndroid.js +92 -18
- package/dist/services/prepareAndroid.js.map +1 -1
- package/dist/services/prepareIos.js +94 -17
- package/dist/services/prepareIos.js.map +1 -1
- package/dist/services/report.js +271 -80
- package/dist/services/report.js.map +1 -1
- package/dist/services/smoke.js +68 -22
- package/dist/services/smoke.js.map +1 -1
- package/dist/services/suiteGenerate.js +43 -6
- package/dist/services/suiteGenerate.js.map +1 -1
- package/dist/services/testSuiteKnowledge.js +78 -33
- package/dist/services/testSuiteKnowledge.js.map +1 -1
- package/dist/session/attach.js +284 -4
- package/dist/session/attach.js.map +1 -1
- package/dist/session/processRegistry.js +433 -0
- package/dist/session/processRegistry.js.map +1 -0
- package/dist/session/progress.js +1 -1
- package/dist/session/progress.js.map +1 -1
- package/dist/session/retention.js +201 -0
- package/dist/session/retention.js.map +1 -0
- package/dist/session/store.js +459 -59
- package/dist/session/store.js.map +1 -1
- package/dist/snapshot/overlays.js +60 -10
- package/dist/snapshot/overlays.js.map +1 -1
- package/dist/snapshot/parse.js +30 -12
- package/dist/snapshot/parse.js.map +1 -1
- package/dist/snapshot/present.js +36 -6
- package/dist/snapshot/present.js.map +1 -1
- package/dist/snapshot/settle.js +39 -9
- package/dist/snapshot/settle.js.map +1 -1
- package/dist/state/consent.js +99 -0
- package/dist/state/consent.js.map +1 -0
- package/dist/state/profile.js +82 -11
- package/dist/state/profile.js.map +1 -1
- package/dist/suite/compile.js +15 -6
- package/dist/suite/compile.js.map +1 -1
- package/dist/suite/lint.js +22 -4
- package/dist/suite/lint.js.map +1 -1
- package/dist/suite/pom.js +75 -44
- package/dist/suite/pom.js.map +1 -1
- package/dist/suite/secretGuard.js +266 -0
- package/dist/suite/secretGuard.js.map +1 -0
- package/dist/suite/testcase.js +15 -11
- package/dist/suite/testcase.js.map +1 -1
- package/dist/testSuite/exporter.js +7 -3
- package/dist/testSuite/exporter.js.map +1 -1
- package/dist/testSuite/generator.js +43 -14
- package/dist/testSuite/generator.js.map +1 -1
- package/dist/testSuite/history.js +1 -1
- package/dist/testSuite/history.js.map +1 -1
- package/dist/testSuite/issueLinks.js +3 -3
- package/dist/testSuite/issueLinks.js.map +1 -1
- package/dist/testSuite/lint.js +4 -4
- package/dist/testSuite/lint.js.map +1 -1
- package/dist/testSuite/merge.js +13 -7
- package/dist/testSuite/merge.js.map +1 -1
- package/dist/testSuite/schema.js +21 -16
- package/dist/testSuite/schema.js.map +1 -1
- package/dist/testSuite/store.js +12 -4
- package/dist/testSuite/store.js.map +1 -1
- package/dist/testSuite/traceability.js +2 -2
- package/dist/testSuite/traceability.js.map +1 -1
- package/dist/tools/act.js +868 -174
- package/dist/tools/act.js.map +1 -1
- package/dist/tools/agent.js +521 -112
- package/dist/tools/agent.js.map +1 -1
- package/dist/tools/appControl.js +82 -19
- package/dist/tools/appControl.js.map +1 -1
- package/dist/tools/appMap.js +393 -189
- package/dist/tools/appMap.js.map +1 -1
- package/dist/tools/build.js +92 -51
- package/dist/tools/build.js.map +1 -1
- package/dist/tools/bundletool.js +141 -40
- package/dist/tools/bundletool.js.map +1 -1
- package/dist/tools/clearOverlay.js +98 -18
- package/dist/tools/clearOverlay.js.map +1 -1
- package/dist/tools/device.js +108 -19
- package/dist/tools/device.js.map +1 -1
- package/dist/tools/doctor.js +98 -35
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/explore.js +110 -42
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/featureTesting.js +152 -130
- package/dist/tools/featureTesting.js.map +1 -1
- package/dist/tools/firstRun.js +94 -66
- package/dist/tools/firstRun.js.map +1 -1
- package/dist/tools/flow.js +347 -132
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/flowRepair.js +42 -10
- package/dist/tools/flowRepair.js.map +1 -1
- package/dist/tools/generate.js +199 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/getArtifact.js +37 -8
- package/dist/tools/getArtifact.js.map +1 -1
- package/dist/tools/health.js +17 -12
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/ios.js +148 -194
- package/dist/tools/ios.js.map +1 -1
- package/dist/tools/issues.js +256 -171
- package/dist/tools/issues.js.map +1 -1
- package/dist/tools/jobs.js +55 -11
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/metro.js +84 -38
- package/dist/tools/metro.js.map +1 -1
- package/dist/tools/mobileAudit.js +83 -26
- package/dist/tools/mobileAudit.js.map +1 -1
- package/dist/tools/network.js +36 -14
- package/dist/tools/network.js.map +1 -1
- package/dist/tools/note.js +31 -17
- package/dist/tools/note.js.map +1 -1
- package/dist/tools/prepareIosTarget.js +31 -14
- package/dist/tools/prepareIosTarget.js.map +1 -1
- package/dist/tools/prepareTarget.js +193 -47
- package/dist/tools/prepareTarget.js.map +1 -1
- package/dist/tools/report.js +16 -8
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/resolveArtifact.js +12 -12
- package/dist/tools/resolveArtifact.js.map +1 -1
- package/dist/tools/resolveTarget.js +81 -18
- package/dist/tools/resolveTarget.js.map +1 -1
- package/dist/tools/screenRecord.js +85 -20
- package/dist/tools/screenRecord.js.map +1 -1
- package/dist/tools/screenshot.js +42 -18
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/smoke.js +29 -12
- package/dist/tools/smoke.js.map +1 -1
- package/dist/tools/snapshot.js +157 -107
- package/dist/tools/snapshot.js.map +1 -1
- package/dist/tools/startSession.js +117 -84
- package/dist/tools/startSession.js.map +1 -1
- package/dist/tools/suite.js +391 -310
- package/dist/tools/suite.js.map +1 -1
- package/dist/tools/testSuite.js +112 -36
- package/dist/tools/testSuite.js.map +1 -1
- package/dist/tools/testThis.js +35 -802
- package/dist/tools/testThis.js.map +1 -1
- package/dist/tools/visual.js +698 -116
- package/dist/tools/visual.js.map +1 -1
- package/dist/tools/wait.js +9 -23
- package/dist/tools/wait.js.map +1 -1
- package/dist/tools/wda.js +312 -54
- package/dist/tools/wda.js.map +1 -1
- package/dist/version.js +37 -64
- package/dist/version.js.map +1 -1
- package/dist/visual/ocr.js +65 -8
- package/dist/visual/ocr.js.map +1 -1
- package/dist/visual/provider.js +87 -15
- package/dist/visual/provider.js.map +1 -1
- package/docs/README.md +24 -10
- package/docs/ci-reports.md +328 -0
- package/docs/concepts.md +227 -0
- package/docs/flows.md +153 -0
- package/docs/mcp-server.md +210 -104
- package/docs/physical-devices.md +84 -0
- package/docs/tools.md +808 -217
- package/package.json +16 -6
- package/dist/appMap/diff.js +0 -60
- package/dist/appMap/diff.js.map +0 -1
- package/dist/automation/maestroIr.js +0 -282
- package/dist/automation/maestroIr.js.map +0 -1
- package/dist/interop/maestro.js +0 -151
- package/dist/interop/maestro.js.map +0 -1
- package/dist/tools/assertVisual.js +0 -74
- package/dist/tools/assertVisual.js.map +0 -1
- package/dist/tools/automationGenerate.js +0 -394
- package/dist/tools/automationGenerate.js.map +0 -1
- package/dist/tools/capabilities.js +0 -209
- package/dist/tools/capabilities.js.map +0 -1
- package/dist/tools/detectContext.js +0 -41
- package/dist/tools/detectContext.js.map +0 -1
- package/dist/tools/flowGenerate.js +0 -55
- package/dist/tools/flowGenerate.js.map +0 -1
- package/dist/tools/history.js +0 -56
- package/dist/tools/history.js.map +0 -1
- package/dist/tools/idling.js +0 -104
- package/dist/tools/idling.js.map +0 -1
- package/dist/tools/inputCapabilities.js +0 -30
- package/dist/tools/inputCapabilities.js.map +0 -1
- package/dist/tools/locator.js +0 -57
- package/dist/tools/locator.js.map +0 -1
- package/dist/tools/maestro.js +0 -78
- package/dist/tools/maestro.js.map +0 -1
- package/dist/tools/permissions.js +0 -149
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/plan.js +0 -49
- package/dist/tools/plan.js.map +0 -1
- package/dist/tools/screenInfo.js +0 -70
- package/dist/tools/screenInfo.js.map +0 -1
- package/dist/tools/seed.js +0 -101
- package/dist/tools/seed.js.map +0 -1
- package/dist/tools/state.js +0 -170
- package/dist/tools/state.js.map +0 -1
- package/dist/tools/visualText.js +0 -61
- package/dist/tools/visualText.js.map +0 -1
package/dist/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';
|
|
@@ -12,8 +42,7 @@ function playwrightStatus(outcome) {
|
|
|
12
42
|
return 'skipped';
|
|
13
43
|
}
|
|
14
44
|
function playwrightDuration(n) {
|
|
15
|
-
|
|
16
|
-
return Math.round(sec * 1000);
|
|
45
|
+
return Math.round(n.steps?.reduce((sum, s) => sum + (s.durationMs ?? 0), 0) ?? 0);
|
|
17
46
|
}
|
|
18
47
|
function playwrightAttachments(n) {
|
|
19
48
|
return (n.artifactUris ?? []).map((uri, i) => ({
|
|
@@ -34,11 +63,13 @@ export function toPlaywrightJson(r) {
|
|
|
34
63
|
title: n.workflow,
|
|
35
64
|
ok: status === 'passed',
|
|
36
65
|
tags: [n.category, n.outcome, evidence?.kind, evidence?.authority].filter(Boolean),
|
|
37
|
-
tests: [
|
|
66
|
+
tests: [
|
|
67
|
+
{
|
|
38
68
|
timeout: 0,
|
|
39
69
|
expectedStatus: 'passed',
|
|
40
70
|
projectName: r.automationBackend?.mode ?? 'swipium',
|
|
41
|
-
results: [
|
|
71
|
+
results: [
|
|
72
|
+
{
|
|
42
73
|
workerIndex: 0,
|
|
43
74
|
status,
|
|
44
75
|
duration: playwrightDuration(n),
|
|
@@ -48,13 +79,15 @@ export function toPlaywrightJson(r) {
|
|
|
48
79
|
stderr: [],
|
|
49
80
|
retry: 0,
|
|
50
81
|
startTime: generatedAt,
|
|
51
|
-
}
|
|
82
|
+
},
|
|
83
|
+
],
|
|
52
84
|
status,
|
|
53
85
|
annotations: [
|
|
54
86
|
...(status === 'skipped' ? [{ type: n.outcome, description: message }] : []),
|
|
55
87
|
...(evidence?.warning ? [{ type: 'evidence', description: evidence.warning }] : []),
|
|
56
88
|
],
|
|
57
|
-
}
|
|
89
|
+
},
|
|
90
|
+
],
|
|
58
91
|
file: `swipium://${r.sessionId}/${n.workflow}`,
|
|
59
92
|
line: 1,
|
|
60
93
|
column: 1,
|
|
@@ -64,24 +97,35 @@ export function toPlaywrightJson(r) {
|
|
|
64
97
|
title: `${f.failureCode ?? f.kind}: ${f.detail}`,
|
|
65
98
|
ok: f.severity !== 'high',
|
|
66
99
|
tags: [f.bucket, f.severity, f.layer].filter(Boolean),
|
|
67
|
-
tests: [
|
|
100
|
+
tests: [
|
|
101
|
+
{
|
|
68
102
|
timeout: 0,
|
|
69
103
|
expectedStatus: 'passed',
|
|
70
104
|
projectName: r.automationBackend?.mode ?? 'swipium',
|
|
71
|
-
results: [
|
|
105
|
+
results: [
|
|
106
|
+
{
|
|
72
107
|
workerIndex: 0,
|
|
73
108
|
status: f.severity === 'high' ? 'failed' : 'passed',
|
|
74
109
|
duration: 0,
|
|
75
|
-
errors: f.severity === 'high'
|
|
110
|
+
errors: f.severity === 'high'
|
|
111
|
+
? [
|
|
112
|
+
{
|
|
113
|
+
message: f.detail,
|
|
114
|
+
location: { file: f.screenshotUri ?? `swipium://${r.sessionId}/finding/${i + 1}`, line: 1, column: 1 },
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
: [],
|
|
76
118
|
attachments: f.screenshotUri ? [{ name: 'screenshot', contentType: 'image/*', path: f.screenshotUri }] : [],
|
|
77
119
|
stdout: [],
|
|
78
120
|
stderr: [],
|
|
79
121
|
retry: 0,
|
|
80
122
|
startTime: generatedAt,
|
|
81
|
-
}
|
|
123
|
+
},
|
|
124
|
+
],
|
|
82
125
|
status: f.severity === 'high' ? 'failed' : 'passed',
|
|
83
126
|
annotations: f.nextStep ? [{ type: 'nextStep', description: f.nextStep }] : [],
|
|
84
|
-
}
|
|
127
|
+
},
|
|
128
|
+
],
|
|
85
129
|
file: f.screenshotUri ?? `swipium://${r.sessionId}/finding/${i + 1}`,
|
|
86
130
|
line: 1,
|
|
87
131
|
column: 1,
|
|
@@ -113,17 +157,19 @@ export function toPlaywrightJson(r) {
|
|
|
113
157
|
},
|
|
114
158
|
projects: [{ name: r.automationBackend?.mode ?? 'swipium' }],
|
|
115
159
|
},
|
|
116
|
-
suites: [
|
|
160
|
+
suites: [
|
|
161
|
+
{
|
|
117
162
|
title: `Swipium ${r.appId ?? 'app'}`,
|
|
118
163
|
file: `swipium://${r.sessionId}/report`,
|
|
119
164
|
specs: allSpecs,
|
|
120
|
-
}
|
|
165
|
+
},
|
|
166
|
+
],
|
|
121
167
|
stats: {
|
|
122
168
|
expected: allSpecs.length - failed - skipped,
|
|
123
169
|
skipped,
|
|
124
170
|
unexpected: failed,
|
|
125
171
|
flaky: r.flakeClassification?.classification === 'flaky' ? 1 : 0,
|
|
126
|
-
duration: Math.round(
|
|
172
|
+
duration: Math.round(r.phaseTimings.totalMs ?? 0),
|
|
127
173
|
},
|
|
128
174
|
errors: failed ? [{ message: r.executiveSummary.nextAction }] : [],
|
|
129
175
|
};
|
|
@@ -185,7 +231,7 @@ function tableCell(s) {
|
|
|
185
231
|
}
|
|
186
232
|
function compactValue(value, maxLength = 180) {
|
|
187
233
|
if (value == null)
|
|
188
|
-
return '
|
|
234
|
+
return '-';
|
|
189
235
|
let raw;
|
|
190
236
|
try {
|
|
191
237
|
raw = typeof value === 'string' ? value : JSON.stringify(value);
|
|
@@ -194,11 +240,11 @@ function compactValue(value, maxLength = 180) {
|
|
|
194
240
|
raw = String(value);
|
|
195
241
|
}
|
|
196
242
|
if (!raw)
|
|
197
|
-
return '
|
|
243
|
+
return '-';
|
|
198
244
|
return raw.length > maxLength ? `${raw.slice(0, Math.max(0, maxLength - 1))}…` : raw;
|
|
199
245
|
}
|
|
200
246
|
function mutationTime(at) {
|
|
201
|
-
return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '
|
|
247
|
+
return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '-';
|
|
202
248
|
}
|
|
203
249
|
function mutationConsentSummary(m) {
|
|
204
250
|
if (!m.consent)
|
|
@@ -213,11 +259,13 @@ function mutationEvidenceSummary(m) {
|
|
|
213
259
|
m.ledgerUri ? `[ledger](${m.ledgerUri})` : '',
|
|
214
260
|
m.consent?.payloadHash ? `payload ${m.consent.payloadHash.slice(0, 12)}` : '',
|
|
215
261
|
].filter(Boolean);
|
|
216
|
-
return parts.length ? parts.join('; ') : '
|
|
262
|
+
return parts.length ? parts.join('; ') : '-';
|
|
217
263
|
}
|
|
218
264
|
export function inlinePrSummary(r) {
|
|
219
265
|
const problem = firstReportProblem(r);
|
|
220
|
-
const reason = problem
|
|
266
|
+
const reason = problem
|
|
267
|
+
? `${problem.workflow ? `${problem.workflow}: ` : ''}${problem.reason}`
|
|
268
|
+
: (r.executiveSummary.reasons[0] ?? 'No blocking failures detected.');
|
|
221
269
|
const category = likelyReportCategory(problem, r);
|
|
222
270
|
const lines = [
|
|
223
271
|
`Swipium: ${statusWord(r.executiveSummary.risk)}`,
|
|
@@ -227,7 +275,7 @@ export function inlinePrSummary(r) {
|
|
|
227
275
|
if (r.automationBackend)
|
|
228
276
|
lines.push(`Backend: ${r.automationBackend.description}.`);
|
|
229
277
|
if (r.readiness?.length)
|
|
230
|
-
lines.push(`Readiness: ${r.readiness.join('
|
|
278
|
+
lines.push(`Readiness: ${r.readiness.join(' > ')}.`);
|
|
231
279
|
if (r.automationReadiness)
|
|
232
280
|
lines.push(`Automation readiness: ${r.automationReadiness.grade} (${r.automationReadiness.score}/100).`);
|
|
233
281
|
lines.push(`Likely category: ${category}.`, `Evidence: ${evidenceSummary(r, problem)}.`, `Next action: ${r.executiveSummary.nextAction}`);
|
|
@@ -236,7 +284,7 @@ export function inlinePrSummary(r) {
|
|
|
236
284
|
export function toMarkdown(r) {
|
|
237
285
|
const L = [];
|
|
238
286
|
const evidenceTaxonomy = r.evidenceTaxonomy ?? evidenceTaxonomyForNotes(r.testOutcomes);
|
|
239
|
-
L.push(`# QA report
|
|
287
|
+
L.push(`# QA report: ${r.appId ?? 'app'}`);
|
|
240
288
|
L.push('');
|
|
241
289
|
L.push('## PR summary', '');
|
|
242
290
|
L.push('```text');
|
|
@@ -260,7 +308,7 @@ export function toMarkdown(r) {
|
|
|
260
308
|
for (const reason of r.executiveSummary.reasons)
|
|
261
309
|
L.push(`- ${reason}`);
|
|
262
310
|
}
|
|
263
|
-
// Issue memory
|
|
311
|
+
// Issue memory: durable, cross-run issue ledger summary + recurrence warnings.
|
|
264
312
|
if (r.issuesMarkdown) {
|
|
265
313
|
L.push('');
|
|
266
314
|
L.push(r.issuesMarkdown);
|
|
@@ -272,7 +320,7 @@ export function toMarkdown(r) {
|
|
|
272
320
|
}
|
|
273
321
|
if (r.readiness?.length) {
|
|
274
322
|
L.push('', '## Capability readiness', '');
|
|
275
|
-
L.push(`- Labels: ${r.readiness.join('
|
|
323
|
+
L.push(`- Labels: ${r.readiness.join(' > ')}.`);
|
|
276
324
|
L.push(`- Highest: ${r.readiness.at(-1)}.`);
|
|
277
325
|
}
|
|
278
326
|
if (r.wda) {
|
|
@@ -323,11 +371,16 @@ export function toMarkdown(r) {
|
|
|
323
371
|
L.push('| Workflow | Outcome | Notes |');
|
|
324
372
|
L.push('| --- | --- | --- |');
|
|
325
373
|
for (const n of r.testOutcomes) {
|
|
326
|
-
const detail = [
|
|
374
|
+
const detail = [
|
|
375
|
+
n.category,
|
|
376
|
+
n.reason,
|
|
377
|
+
n.missingPrecondition ? `missing: ${n.missingPrecondition}` : '',
|
|
378
|
+
n.recommendedSetup ? `setup: ${n.recommendedSetup}` : '',
|
|
379
|
+
]
|
|
327
380
|
.filter(Boolean)
|
|
328
381
|
.join('; ')
|
|
329
382
|
.replace(/\|/g, '\\|');
|
|
330
|
-
L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '
|
|
383
|
+
L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '-'} |`);
|
|
331
384
|
}
|
|
332
385
|
L.push('', '## Evidence quality', '');
|
|
333
386
|
L.push(`- Deterministic structured locator: ${evidenceTaxonomy.counts.structured_locator}.`);
|
|
@@ -337,7 +390,7 @@ export function toMarkdown(r) {
|
|
|
337
390
|
L.push('', '| Workflow | Evidence | Authority | Notes |');
|
|
338
391
|
L.push('| --- | --- | --- | --- |');
|
|
339
392
|
for (const ev of evidenceTaxonomy.assessments) {
|
|
340
|
-
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) : '-'} |`);
|
|
341
394
|
}
|
|
342
395
|
}
|
|
343
396
|
if (r.flakeClassification && r.flakeClassification.repeat > 1) {
|
|
@@ -353,7 +406,7 @@ export function toMarkdown(r) {
|
|
|
353
406
|
L.push('| Fixture | Field | Generator | Variable | Value | Evidence |');
|
|
354
407
|
L.push('| --- | --- | --- | --- | --- | --- |');
|
|
355
408
|
for (const g of r.generatedValues) {
|
|
356
|
-
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})` : '-'} |`);
|
|
357
410
|
}
|
|
358
411
|
}
|
|
359
412
|
if (r.findings.length) {
|
|
@@ -363,7 +416,7 @@ export function toMarkdown(r) {
|
|
|
363
416
|
const bucket = f.bucket ? ` ${f.bucket}` : '';
|
|
364
417
|
const retry = f.retrySafe == null ? '' : ` retrySafe=${f.retrySafe}`;
|
|
365
418
|
const next = f.nextStep ? ` Next: ${f.nextStep}` : '';
|
|
366
|
-
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}`);
|
|
367
420
|
}
|
|
368
421
|
}
|
|
369
422
|
if (r.environmentChanges.length || r.guardrailOverrides.length) {
|
|
@@ -384,33 +437,36 @@ export function toMarkdown(r) {
|
|
|
384
437
|
L.push('| Time | Tool | Action | Risk | Status | Consent | Evidence | Target / detail |');
|
|
385
438
|
L.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
|
|
386
439
|
for (const m of r.mutationLedger) {
|
|
387
|
-
const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : '']
|
|
388
|
-
.filter((part) => part && part !== '—')
|
|
389
|
-
.join('; ') || '—';
|
|
440
|
+
const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : ''].filter((part) => part && part !== '-').join('; ') || '-';
|
|
390
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)} |`);
|
|
391
442
|
}
|
|
392
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
|
+
}
|
|
393
448
|
L.push('', '## Timing & artifacts', '');
|
|
394
|
-
|
|
449
|
+
const fmtS = (ms) => `${Math.round((ms ?? 0) / 1000)}s`;
|
|
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)}` : ''}`);
|
|
395
451
|
const d = r.phaseTimings.diagnostics;
|
|
396
452
|
if (d) {
|
|
397
453
|
const parts = [
|
|
398
|
-
d.
|
|
399
|
-
d.
|
|
400
|
-
d.
|
|
401
|
-
d.
|
|
402
|
-
d.
|
|
403
|
-
d.
|
|
404
|
-
d.
|
|
405
|
-
d.
|
|
406
|
-
d.
|
|
407
|
-
d.
|
|
408
|
-
d.
|
|
409
|
-
d.
|
|
410
|
-
d.
|
|
411
|
-
d.
|
|
412
|
-
d.
|
|
413
|
-
d.
|
|
454
|
+
d.simulatorBootMs != null ? `boot ${fmtS(d.simulatorBootMs)}` : '',
|
|
455
|
+
d.appInstallMs != null ? `install ${fmtS(d.appInstallMs)}` : '',
|
|
456
|
+
d.appLaunchMs != null ? `launch ${fmtS(d.appLaunchMs)}` : '',
|
|
457
|
+
d.wdaBuildMs != null ? `WDA build ${fmtS(d.wdaBuildMs)}` : '',
|
|
458
|
+
d.wdaStartMs != null ? `WDA start ${fmtS(d.wdaStartMs)}` : '',
|
|
459
|
+
d.wdaReuseCheckMs != null ? `WDA reuse check ${fmtS(d.wdaReuseCheckMs)}` : '',
|
|
460
|
+
d.wdaStartupWaitMs != null ? `WDA ready wait ${fmtS(d.wdaStartupWaitMs)}` : '',
|
|
461
|
+
d.wdaSessionCreateMs != null ? `WDA session ${fmtS(d.wdaSessionCreateMs)}` : '',
|
|
462
|
+
d.wdaSourceMs != null ? `WDA source ${fmtS(d.wdaSourceMs)}` : '',
|
|
463
|
+
d.wdaFindElementMs != null ? `WDA find ${fmtS(d.wdaFindElementMs)}` : '',
|
|
464
|
+
d.wdaTapMs != null ? `WDA tap ${fmtS(d.wdaTapMs)}` : '',
|
|
465
|
+
d.wdaTypeMs != null ? `WDA type ${fmtS(d.wdaTypeMs)}` : '',
|
|
466
|
+
d.wdaClearMs != null ? `WDA clear ${fmtS(d.wdaClearMs)}` : '',
|
|
467
|
+
d.wdaScreenshotMs != null ? `WDA screenshot ${fmtS(d.wdaScreenshotMs)}` : '',
|
|
468
|
+
d.flowRuntimeMs != null ? `flows ${fmtS(d.flowRuntimeMs)}` : '',
|
|
469
|
+
d.waitMs != null ? `wait ${fmtS(d.waitMs)}` : '',
|
|
414
470
|
d.screenshotCount != null ? `screenshots ${d.screenshotCount}` : '',
|
|
415
471
|
].filter(Boolean);
|
|
416
472
|
if (parts.length)
|
|
@@ -422,35 +478,356 @@ export function toMarkdown(r) {
|
|
|
422
478
|
L.push('', '---', '_Generated by Swipium._');
|
|
423
479
|
return L.join('\n');
|
|
424
480
|
}
|
|
425
|
-
|
|
426
|
-
|
|
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)}`;
|
|
535
|
+
}
|
|
536
|
+
function findingPolicyKey(f) {
|
|
537
|
+
return `finding:${f.kind}: ${f.failureCode ?? f.kind}`;
|
|
427
538
|
}
|
|
428
|
-
|
|
429
|
-
|
|
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) {
|
|
430
555
|
const cases = r.testOutcomes;
|
|
431
|
-
const
|
|
432
|
-
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;
|
|
433
569
|
const suiteName = `swipium.${r.appId ?? 'app'}`;
|
|
570
|
+
const timeSec = (r.phaseTimings.totalMs ?? 0) / 1000;
|
|
434
571
|
const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
|
|
435
|
-
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
|
+
}
|
|
436
580
|
for (const n of cases) {
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
lines.push(` <testcase name="${name}" classname="${xmlEscape(suiteName)}">`);
|
|
446
|
-
lines.push(` <skipped message="${msg}"></skipped>`);
|
|
447
|
-
lines.push(` </testcase>`);
|
|
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>`);
|
|
448
589
|
}
|
|
449
|
-
|
|
450
|
-
|
|
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);
|
|
451
611
|
}
|
|
612
|
+
lines.push(' </testsuite>');
|
|
452
613
|
}
|
|
453
|
-
lines.push('</
|
|
614
|
+
lines.push('</testsuites>');
|
|
454
615
|
return lines.join('\n');
|
|
455
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/swipium/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
|
+
}
|
|
456
833
|
//# sourceMappingURL=export.js.map
|