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.
Files changed (541) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/README.md +175 -219
  3. package/THREAT_MODEL.md +223 -47
  4. package/dist/appMap/automationLink.js +39 -26
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +68 -20
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +31 -5
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +23 -5
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +44 -8
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +24 -6
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/fsWalk.js +19 -2
  17. package/dist/appMap/fsWalk.js.map +1 -1
  18. package/dist/appMap/issues.js +10 -30
  19. package/dist/appMap/issues.js.map +1 -1
  20. package/dist/appMap/migrations.js +6 -6
  21. package/dist/appMap/migrations.js.map +1 -1
  22. package/dist/appMap/prelaunch.js +2 -2
  23. package/dist/appMap/prelaunch.js.map +1 -1
  24. package/dist/appMap/projectRegistry.js +83 -11
  25. package/dist/appMap/projectRegistry.js.map +1 -1
  26. package/dist/appMap/provenance.js +2 -2
  27. package/dist/appMap/provenance.js.map +1 -1
  28. package/dist/appMap/query.js +21 -5
  29. package/dist/appMap/query.js.map +1 -1
  30. package/dist/appMap/runtimeMerge.js +12 -5
  31. package/dist/appMap/runtimeMerge.js.map +1 -1
  32. package/dist/appMap/schema.js +1 -1
  33. package/dist/appMap/schema.js.map +1 -1
  34. package/dist/appMap/screenMatch.js +6 -2
  35. package/dist/appMap/screenMatch.js.map +1 -1
  36. package/dist/appMap/staticScan.js +151 -39
  37. package/dist/appMap/staticScan.js.map +1 -1
  38. package/dist/appMap/store.js +58 -85
  39. package/dist/appMap/store.js.map +1 -1
  40. package/dist/appMap/tsAstScan.js +34 -12
  41. package/dist/appMap/tsAstScan.js.map +1 -1
  42. package/dist/artifacts/bundletool.js +49 -12
  43. package/dist/artifacts/bundletool.js.map +1 -1
  44. package/dist/artifacts/resolve.js +41 -14
  45. package/dist/artifacts/resolve.js.map +1 -1
  46. package/dist/automation/capabilities.js +112 -30
  47. package/dist/automation/capabilities.js.map +1 -1
  48. package/dist/automation/gestures.js +6 -2
  49. package/dist/automation/gestures.js.map +1 -1
  50. package/dist/automation/plan.js +71 -23
  51. package/dist/automation/plan.js.map +1 -1
  52. package/dist/automation/report.js +15 -5
  53. package/dist/automation/report.js.map +1 -1
  54. package/dist/automation/selectors.js +22 -28
  55. package/dist/automation/selectors.js.map +1 -1
  56. package/dist/automation/types.js +1 -1
  57. package/dist/automation/types.js.map +1 -1
  58. package/dist/automation/waits.js +14 -8
  59. package/dist/automation/waits.js.map +1 -1
  60. package/dist/automation/webview.js +9 -3
  61. package/dist/automation/webview.js.map +1 -1
  62. package/dist/automationGen/appiumModel.js +16 -8
  63. package/dist/automationGen/appiumModel.js.map +1 -1
  64. package/dist/automationGen/ciEmitter.js +6 -6
  65. package/dist/automationGen/ciEmitter.js.map +1 -1
  66. package/dist/automationGen/identifiers.js +224 -0
  67. package/dist/automationGen/identifiers.js.map +1 -0
  68. package/dist/automationGen/jsEmitter.js +288 -121
  69. package/dist/automationGen/jsEmitter.js.map +1 -1
  70. package/dist/automationGen/packagePatch.js +2 -2
  71. package/dist/automationGen/packagePatch.js.map +1 -1
  72. package/dist/automationGen/platformResolve.js +36 -0
  73. package/dist/automationGen/platformResolve.js.map +1 -0
  74. package/dist/automationGen/projectProfile.js +76 -27
  75. package/dist/automationGen/projectProfile.js.map +1 -1
  76. package/dist/automationGen/pythonEmitter.js +278 -107
  77. package/dist/automationGen/pythonEmitter.js.map +1 -1
  78. package/dist/automationGen/readmeEmitter.js +7 -6
  79. package/dist/automationGen/readmeEmitter.js.map +1 -1
  80. package/dist/automationGen/run.js +388 -0
  81. package/dist/automationGen/run.js.map +1 -0
  82. package/dist/automationGen/suitePlan.js +17 -10
  83. package/dist/automationGen/suitePlan.js.map +1 -1
  84. package/dist/automationGen/validation.js +52 -12
  85. package/dist/automationGen/validation.js.map +1 -1
  86. package/dist/build/parseBuildLog.js +235 -31
  87. package/dist/build/parseBuildLog.js.map +1 -1
  88. package/dist/build/plan.js +39 -18
  89. package/dist/build/plan.js.map +1 -1
  90. package/dist/ci/preflight.js +13 -4
  91. package/dist/ci/preflight.js.map +1 -1
  92. package/dist/cli/gc.js +74 -0
  93. package/dist/cli/gc.js.map +1 -0
  94. package/dist/cli/init.js +217 -26
  95. package/dist/cli/init.js.map +1 -1
  96. package/dist/cli/main.js +50 -0
  97. package/dist/cli/main.js.map +1 -0
  98. package/dist/cli/report.js +218 -0
  99. package/dist/cli/report.js.map +1 -0
  100. package/dist/cli/scan.js +36 -15
  101. package/dist/cli/scan.js.map +1 -1
  102. package/dist/cli/suite.js +7 -7
  103. package/dist/cli/suite.js.map +1 -1
  104. package/dist/cli/verify.js +7 -7
  105. package/dist/cli/verify.js.map +1 -1
  106. package/dist/consent/consent.js +162 -5
  107. package/dist/consent/consent.js.map +1 -1
  108. package/dist/context/detect.js +55 -18
  109. package/dist/context/detect.js.map +1 -1
  110. package/dist/context/findApps.js +22 -8
  111. package/dist/context/findApps.js.map +1 -1
  112. package/dist/context/projectRoot.js +174 -19
  113. package/dist/context/projectRoot.js.map +1 -1
  114. package/dist/context/scan.js +1 -1
  115. package/dist/context/scan.js.map +1 -1
  116. package/dist/core/capabilityGroups.js +90 -0
  117. package/dist/core/capabilityGroups.js.map +1 -0
  118. package/dist/core/target.js +10 -8
  119. package/dist/core/target.js.map +1 -1
  120. package/dist/core/targetPlan.js +160 -39
  121. package/dist/core/targetPlan.js.map +1 -1
  122. package/dist/drivers/DirectDriver.js +317 -43
  123. package/dist/drivers/DirectDriver.js.map +1 -1
  124. package/dist/drivers/SimctlDriver.js +124 -8
  125. package/dist/drivers/SimctlDriver.js.map +1 -1
  126. package/dist/drivers/WdaDriver.js +365 -55
  127. package/dist/drivers/WdaDriver.js.map +1 -1
  128. package/dist/explore/candidates.js +7 -3
  129. package/dist/explore/candidates.js.map +1 -1
  130. package/dist/explore/graph.js +15 -6
  131. package/dist/explore/graph.js.map +1 -1
  132. package/dist/explore/memory.js +4 -1
  133. package/dist/explore/memory.js.map +1 -1
  134. package/dist/explore/planner.js +54 -9
  135. package/dist/explore/planner.js.map +1 -1
  136. package/dist/explore/policy.js +11 -6
  137. package/dist/explore/policy.js.map +1 -1
  138. package/dist/explore/runner.js +363 -190
  139. package/dist/explore/runner.js.map +1 -1
  140. package/dist/explore/signatures.js +4 -2
  141. package/dist/explore/signatures.js.map +1 -1
  142. package/dist/explore/suite.js +3 -1
  143. package/dist/explore/suite.js.map +1 -1
  144. package/dist/featureTesting/executionBootstrap.js +137 -29
  145. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  146. package/dist/featureTesting/featureMap.js +6 -3
  147. package/dist/featureTesting/featureMap.js.map +1 -1
  148. package/dist/featureTesting/featureScope.js +101 -20
  149. package/dist/featureTesting/featureScope.js.map +1 -1
  150. package/dist/featureTesting/mapFeatureScope.js +44 -15
  151. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  152. package/dist/featureTesting/objectiveModel.js +15 -11
  153. package/dist/featureTesting/objectiveModel.js.map +1 -1
  154. package/dist/featureTesting/resultMerge.js +16 -8
  155. package/dist/featureTesting/resultMerge.js.map +1 -1
  156. package/dist/featureTesting/sources.js +8 -5
  157. package/dist/featureTesting/sources.js.map +1 -1
  158. package/dist/featureTesting/suiteBridge.js +8 -2
  159. package/dist/featureTesting/suiteBridge.js.map +1 -1
  160. package/dist/featureTesting/synonyms.js +32 -4
  161. package/dist/featureTesting/synonyms.js.map +1 -1
  162. package/dist/featureTesting/testCaseFactory.js +27 -15
  163. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  164. package/dist/featureTesting/testPlan.js +34 -12
  165. package/dist/featureTesting/testPlan.js.map +1 -1
  166. package/dist/firstRun/authStateMachine.js +1 -1
  167. package/dist/firstRun/authStateMachine.js.map +1 -1
  168. package/dist/firstRun/classifyScreen.js +9 -6
  169. package/dist/firstRun/classifyScreen.js.map +1 -1
  170. package/dist/firstRun/firstRunPlanner.js +16 -13
  171. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  172. package/dist/firstRun/firstRunRunner.js +92 -31
  173. package/dist/firstRun/firstRunRunner.js.map +1 -1
  174. package/dist/firstRun/generatedDataPolicy.js +25 -15
  175. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  176. package/dist/firstRun/inputPlanner.js +45 -11
  177. package/dist/firstRun/inputPlanner.js.map +1 -1
  178. package/dist/firstRun/onboardingStateMachine.js +1 -1
  179. package/dist/firstRun/onboardingStateMachine.js.map +1 -1
  180. package/dist/firstRun/paywallPolicy.js +3 -3
  181. package/dist/firstRun/paywallPolicy.js.map +1 -1
  182. package/dist/firstRun/types.js +1 -1
  183. package/dist/firstRun/types.js.map +1 -1
  184. package/dist/fixtures/catalog.js +72 -9
  185. package/dist/fixtures/catalog.js.map +1 -1
  186. package/dist/fixtures/load.js +50 -0
  187. package/dist/fixtures/load.js.map +1 -0
  188. package/dist/flows/discover.js +2 -2
  189. package/dist/flows/discover.js.map +1 -1
  190. package/dist/flows/generate.js +68 -17
  191. package/dist/flows/generate.js.map +1 -1
  192. package/dist/flows/lint.js +88 -14
  193. package/dist/flows/lint.js.map +1 -1
  194. package/dist/flows/pack.js +21 -5
  195. package/dist/flows/pack.js.map +1 -1
  196. package/dist/flows/paths.js +57 -0
  197. package/dist/flows/paths.js.map +1 -0
  198. package/dist/flows/repair.js +176 -37
  199. package/dist/flows/repair.js.map +1 -1
  200. package/dist/flows/run.js +308 -99
  201. package/dist/flows/run.js.map +1 -1
  202. package/dist/flows/schema.js +107 -25
  203. package/dist/flows/schema.js.map +1 -1
  204. package/dist/flows/seedExec.js +8 -4
  205. package/dist/flows/seedExec.js.map +1 -1
  206. package/dist/flows/templates.js +5 -5
  207. package/dist/index.js +50 -8
  208. package/dist/index.js.map +1 -1
  209. package/dist/ios/signing.js +32 -3
  210. package/dist/ios/signing.js.map +1 -1
  211. package/dist/issues/classify.js +28 -20
  212. package/dist/issues/classify.js.map +1 -1
  213. package/dist/issues/fingerprint.js +35 -9
  214. package/dist/issues/fingerprint.js.map +1 -1
  215. package/dist/issues/index.js +93 -46
  216. package/dist/issues/index.js.map +1 -1
  217. package/dist/issues/metrics.js +6 -5
  218. package/dist/issues/metrics.js.map +1 -1
  219. package/dist/issues/recurrence.js +35 -8
  220. package/dist/issues/recurrence.js.map +1 -1
  221. package/dist/issues/report.js +12 -8
  222. package/dist/issues/report.js.map +1 -1
  223. package/dist/issues/reportBridge.js +40 -9
  224. package/dist/issues/reportBridge.js.map +1 -1
  225. package/dist/issues/schema.js +2 -2
  226. package/dist/issues/schema.js.map +1 -1
  227. package/dist/issues/sourceRevision.js +2 -2
  228. package/dist/issues/sourceRevision.js.map +1 -1
  229. package/dist/issues/store.js +110 -32
  230. package/dist/issues/store.js.map +1 -1
  231. package/dist/lib/abortScope.js +48 -0
  232. package/dist/lib/abortScope.js.map +1 -0
  233. package/dist/lib/android.js +84 -8
  234. package/dist/lib/android.js.map +1 -1
  235. package/dist/lib/coordSpace.js +1 -1
  236. package/dist/lib/coordSpace.js.map +1 -1
  237. package/dist/lib/device.js +53 -12
  238. package/dist/lib/device.js.map +1 -1
  239. package/dist/lib/gestures.js +141 -0
  240. package/dist/lib/gestures.js.map +1 -0
  241. package/dist/lib/gitignore.js +4 -3
  242. package/dist/lib/gitignore.js.map +1 -1
  243. package/dist/lib/image.js +12 -3
  244. package/dist/lib/image.js.map +1 -1
  245. package/dist/lib/lockfile.js +399 -0
  246. package/dist/lib/lockfile.js.map +1 -0
  247. package/dist/lib/needsInput.js +9 -6
  248. package/dist/lib/needsInput.js.map +1 -1
  249. package/dist/lib/png.js +4 -3
  250. package/dist/lib/png.js.map +1 -1
  251. package/dist/lib/redact.js +170 -9
  252. package/dist/lib/redact.js.map +1 -1
  253. package/dist/lib/result.js +94 -10
  254. package/dist/lib/result.js.map +1 -1
  255. package/dist/lib/schemaHash.js +7 -5
  256. package/dist/lib/schemaHash.js.map +1 -1
  257. package/dist/lib/sensitive.js +6 -3
  258. package/dist/lib/sensitive.js.map +1 -1
  259. package/dist/lib/simctl.js +3 -3
  260. package/dist/lib/simctl.js.map +1 -1
  261. package/dist/lib/spawn.js +78 -17
  262. package/dist/lib/spawn.js.map +1 -1
  263. package/dist/lib/toolAnnotations.js +114 -0
  264. package/dist/lib/toolAnnotations.js.map +1 -0
  265. package/dist/lib/wda.js +321 -25
  266. package/dist/lib/wda.js.map +1 -1
  267. package/dist/lib/wdaConfig.js +12 -5
  268. package/dist/lib/wdaConfig.js.map +1 -1
  269. package/dist/lib/wdaTune.js +52 -23
  270. package/dist/lib/wdaTune.js.map +1 -1
  271. package/dist/mobileAudit/checks.js +150 -28
  272. package/dist/mobileAudit/checks.js.map +1 -1
  273. package/dist/mobileAudit/evidence.js +4 -4
  274. package/dist/mobileAudit/evidence.js.map +1 -1
  275. package/dist/mobileAudit/profiles.js +121 -20
  276. package/dist/mobileAudit/profiles.js.map +1 -1
  277. package/dist/mobileAudit/results.js +9 -4
  278. package/dist/mobileAudit/results.js.map +1 -1
  279. package/dist/mobileAudit/runner.js +214 -35
  280. package/dist/mobileAudit/runner.js.map +1 -1
  281. package/dist/oracle/auth.js +1 -1
  282. package/dist/oracle/auth.js.map +1 -1
  283. package/dist/oracle/failures.js +1085 -145
  284. package/dist/oracle/failures.js.map +1 -1
  285. package/dist/oracle/health.js +148 -26
  286. package/dist/oracle/health.js.map +1 -1
  287. package/dist/oracle/locator.js +148 -19
  288. package/dist/oracle/locator.js.map +1 -1
  289. package/dist/oracle/record.js +3 -3
  290. package/dist/oracle/record.js.map +1 -1
  291. package/dist/orchestration/envelope.js +2 -2
  292. package/dist/orchestration/envelope.js.map +1 -1
  293. package/dist/orchestration/goal.js +7 -13
  294. package/dist/orchestration/goal.js.map +1 -1
  295. package/dist/orchestration/testThis/execute.js +167 -0
  296. package/dist/orchestration/testThis/execute.js.map +1 -0
  297. package/dist/orchestration/testThis/pipeline.js +413 -0
  298. package/dist/orchestration/testThis/pipeline.js.map +1 -0
  299. package/dist/orchestration/testThis/plan.js +519 -0
  300. package/dist/orchestration/testThis/plan.js.map +1 -0
  301. package/dist/orchestration/testThis/sessionIntent.js +72 -0
  302. package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
  303. package/dist/orchestration/testThis/terminal.js +136 -0
  304. package/dist/orchestration/testThis/terminal.js.map +1 -0
  305. package/dist/orchestration/testThis/types.js +12 -0
  306. package/dist/orchestration/testThis/types.js.map +1 -0
  307. package/dist/plan/plan.js +9 -9
  308. package/dist/plan/plan.js.map +1 -1
  309. package/dist/prompts/index.js +24 -24
  310. package/dist/prompts/index.js.map +1 -1
  311. package/dist/report/coverage.js +0 -1
  312. package/dist/report/coverage.js.map +1 -1
  313. package/dist/report/evidence.js.map +1 -1
  314. package/dist/report/export.js +449 -72
  315. package/dist/report/export.js.map +1 -1
  316. package/dist/report/findingsDedupe.js +41 -0
  317. package/dist/report/findingsDedupe.js.map +1 -0
  318. package/dist/report/flake.js +13 -14
  319. package/dist/report/flake.js.map +1 -1
  320. package/dist/report/history.js +72 -38
  321. package/dist/report/history.js.map +1 -1
  322. package/dist/report/policy.js +18 -7
  323. package/dist/report/policy.js.map +1 -1
  324. package/dist/report/qaLevel.js +10 -16
  325. package/dist/report/qaLevel.js.map +1 -1
  326. package/dist/report/readiness.js +42 -12
  327. package/dist/report/readiness.js.map +1 -1
  328. package/dist/report/sarifSources.js +160 -0
  329. package/dist/report/sarifSources.js.map +1 -0
  330. package/dist/report/summary.js +2 -2
  331. package/dist/report/summary.js.map +1 -1
  332. package/dist/report/testCatalog.js +4 -4
  333. package/dist/report/testCatalog.js.map +1 -1
  334. package/dist/report/timing.js +23 -23
  335. package/dist/report/timing.js.map +1 -1
  336. package/dist/report/toolHealth.js +124 -0
  337. package/dist/report/toolHealth.js.map +1 -0
  338. package/dist/server.js +501 -69
  339. package/dist/server.js.map +1 -1
  340. package/dist/services/automationGenerate.js +8 -2
  341. package/dist/services/automationGenerate.js.map +1 -1
  342. package/dist/services/build.js +10 -4
  343. package/dist/services/build.js.map +1 -1
  344. package/dist/services/flowGenerate.js +75 -0
  345. package/dist/services/flowGenerate.js.map +1 -0
  346. package/dist/services/preflight.js +40 -8
  347. package/dist/services/preflight.js.map +1 -1
  348. package/dist/services/prepareAndroid.js +92 -18
  349. package/dist/services/prepareAndroid.js.map +1 -1
  350. package/dist/services/prepareIos.js +94 -17
  351. package/dist/services/prepareIos.js.map +1 -1
  352. package/dist/services/report.js +271 -80
  353. package/dist/services/report.js.map +1 -1
  354. package/dist/services/smoke.js +68 -22
  355. package/dist/services/smoke.js.map +1 -1
  356. package/dist/services/suiteGenerate.js +43 -6
  357. package/dist/services/suiteGenerate.js.map +1 -1
  358. package/dist/services/testSuiteKnowledge.js +78 -33
  359. package/dist/services/testSuiteKnowledge.js.map +1 -1
  360. package/dist/session/attach.js +284 -4
  361. package/dist/session/attach.js.map +1 -1
  362. package/dist/session/processRegistry.js +433 -0
  363. package/dist/session/processRegistry.js.map +1 -0
  364. package/dist/session/progress.js +1 -1
  365. package/dist/session/progress.js.map +1 -1
  366. package/dist/session/retention.js +201 -0
  367. package/dist/session/retention.js.map +1 -0
  368. package/dist/session/store.js +459 -59
  369. package/dist/session/store.js.map +1 -1
  370. package/dist/snapshot/overlays.js +60 -10
  371. package/dist/snapshot/overlays.js.map +1 -1
  372. package/dist/snapshot/parse.js +30 -12
  373. package/dist/snapshot/parse.js.map +1 -1
  374. package/dist/snapshot/present.js +36 -6
  375. package/dist/snapshot/present.js.map +1 -1
  376. package/dist/snapshot/settle.js +39 -9
  377. package/dist/snapshot/settle.js.map +1 -1
  378. package/dist/state/consent.js +99 -0
  379. package/dist/state/consent.js.map +1 -0
  380. package/dist/state/profile.js +82 -11
  381. package/dist/state/profile.js.map +1 -1
  382. package/dist/suite/compile.js +15 -6
  383. package/dist/suite/compile.js.map +1 -1
  384. package/dist/suite/lint.js +22 -4
  385. package/dist/suite/lint.js.map +1 -1
  386. package/dist/suite/pom.js +75 -44
  387. package/dist/suite/pom.js.map +1 -1
  388. package/dist/suite/secretGuard.js +266 -0
  389. package/dist/suite/secretGuard.js.map +1 -0
  390. package/dist/suite/testcase.js +15 -11
  391. package/dist/suite/testcase.js.map +1 -1
  392. package/dist/testSuite/exporter.js +7 -3
  393. package/dist/testSuite/exporter.js.map +1 -1
  394. package/dist/testSuite/generator.js +43 -14
  395. package/dist/testSuite/generator.js.map +1 -1
  396. package/dist/testSuite/history.js +1 -1
  397. package/dist/testSuite/history.js.map +1 -1
  398. package/dist/testSuite/issueLinks.js +3 -3
  399. package/dist/testSuite/issueLinks.js.map +1 -1
  400. package/dist/testSuite/lint.js +4 -4
  401. package/dist/testSuite/lint.js.map +1 -1
  402. package/dist/testSuite/merge.js +13 -7
  403. package/dist/testSuite/merge.js.map +1 -1
  404. package/dist/testSuite/schema.js +21 -16
  405. package/dist/testSuite/schema.js.map +1 -1
  406. package/dist/testSuite/store.js +12 -4
  407. package/dist/testSuite/store.js.map +1 -1
  408. package/dist/testSuite/traceability.js +2 -2
  409. package/dist/testSuite/traceability.js.map +1 -1
  410. package/dist/tools/act.js +868 -174
  411. package/dist/tools/act.js.map +1 -1
  412. package/dist/tools/agent.js +521 -112
  413. package/dist/tools/agent.js.map +1 -1
  414. package/dist/tools/appControl.js +82 -19
  415. package/dist/tools/appControl.js.map +1 -1
  416. package/dist/tools/appMap.js +393 -189
  417. package/dist/tools/appMap.js.map +1 -1
  418. package/dist/tools/build.js +92 -51
  419. package/dist/tools/build.js.map +1 -1
  420. package/dist/tools/bundletool.js +141 -40
  421. package/dist/tools/bundletool.js.map +1 -1
  422. package/dist/tools/clearOverlay.js +98 -18
  423. package/dist/tools/clearOverlay.js.map +1 -1
  424. package/dist/tools/device.js +108 -19
  425. package/dist/tools/device.js.map +1 -1
  426. package/dist/tools/doctor.js +98 -35
  427. package/dist/tools/doctor.js.map +1 -1
  428. package/dist/tools/explore.js +110 -42
  429. package/dist/tools/explore.js.map +1 -1
  430. package/dist/tools/featureTesting.js +152 -130
  431. package/dist/tools/featureTesting.js.map +1 -1
  432. package/dist/tools/firstRun.js +94 -66
  433. package/dist/tools/firstRun.js.map +1 -1
  434. package/dist/tools/flow.js +347 -132
  435. package/dist/tools/flow.js.map +1 -1
  436. package/dist/tools/flowRepair.js +42 -10
  437. package/dist/tools/flowRepair.js.map +1 -1
  438. package/dist/tools/generate.js +199 -0
  439. package/dist/tools/generate.js.map +1 -0
  440. package/dist/tools/getArtifact.js +37 -8
  441. package/dist/tools/getArtifact.js.map +1 -1
  442. package/dist/tools/health.js +17 -12
  443. package/dist/tools/health.js.map +1 -1
  444. package/dist/tools/ios.js +148 -194
  445. package/dist/tools/ios.js.map +1 -1
  446. package/dist/tools/issues.js +256 -171
  447. package/dist/tools/issues.js.map +1 -1
  448. package/dist/tools/jobs.js +55 -11
  449. package/dist/tools/jobs.js.map +1 -1
  450. package/dist/tools/metro.js +84 -38
  451. package/dist/tools/metro.js.map +1 -1
  452. package/dist/tools/mobileAudit.js +83 -26
  453. package/dist/tools/mobileAudit.js.map +1 -1
  454. package/dist/tools/network.js +36 -14
  455. package/dist/tools/network.js.map +1 -1
  456. package/dist/tools/note.js +31 -17
  457. package/dist/tools/note.js.map +1 -1
  458. package/dist/tools/prepareIosTarget.js +31 -14
  459. package/dist/tools/prepareIosTarget.js.map +1 -1
  460. package/dist/tools/prepareTarget.js +193 -47
  461. package/dist/tools/prepareTarget.js.map +1 -1
  462. package/dist/tools/report.js +16 -8
  463. package/dist/tools/report.js.map +1 -1
  464. package/dist/tools/resolveArtifact.js +12 -12
  465. package/dist/tools/resolveArtifact.js.map +1 -1
  466. package/dist/tools/resolveTarget.js +81 -18
  467. package/dist/tools/resolveTarget.js.map +1 -1
  468. package/dist/tools/screenRecord.js +85 -20
  469. package/dist/tools/screenRecord.js.map +1 -1
  470. package/dist/tools/screenshot.js +42 -18
  471. package/dist/tools/screenshot.js.map +1 -1
  472. package/dist/tools/smoke.js +29 -12
  473. package/dist/tools/smoke.js.map +1 -1
  474. package/dist/tools/snapshot.js +157 -107
  475. package/dist/tools/snapshot.js.map +1 -1
  476. package/dist/tools/startSession.js +117 -84
  477. package/dist/tools/startSession.js.map +1 -1
  478. package/dist/tools/suite.js +391 -310
  479. package/dist/tools/suite.js.map +1 -1
  480. package/dist/tools/testSuite.js +112 -36
  481. package/dist/tools/testSuite.js.map +1 -1
  482. package/dist/tools/testThis.js +35 -802
  483. package/dist/tools/testThis.js.map +1 -1
  484. package/dist/tools/visual.js +698 -116
  485. package/dist/tools/visual.js.map +1 -1
  486. package/dist/tools/wait.js +9 -23
  487. package/dist/tools/wait.js.map +1 -1
  488. package/dist/tools/wda.js +312 -54
  489. package/dist/tools/wda.js.map +1 -1
  490. package/dist/version.js +37 -64
  491. package/dist/version.js.map +1 -1
  492. package/dist/visual/ocr.js +65 -8
  493. package/dist/visual/ocr.js.map +1 -1
  494. package/dist/visual/provider.js +87 -15
  495. package/dist/visual/provider.js.map +1 -1
  496. package/docs/README.md +24 -10
  497. package/docs/ci-reports.md +328 -0
  498. package/docs/concepts.md +227 -0
  499. package/docs/flows.md +153 -0
  500. package/docs/mcp-server.md +210 -104
  501. package/docs/physical-devices.md +84 -0
  502. package/docs/tools.md +808 -217
  503. package/package.json +16 -6
  504. package/dist/appMap/diff.js +0 -60
  505. package/dist/appMap/diff.js.map +0 -1
  506. package/dist/automation/maestroIr.js +0 -282
  507. package/dist/automation/maestroIr.js.map +0 -1
  508. package/dist/interop/maestro.js +0 -151
  509. package/dist/interop/maestro.js.map +0 -1
  510. package/dist/tools/assertVisual.js +0 -74
  511. package/dist/tools/assertVisual.js.map +0 -1
  512. package/dist/tools/automationGenerate.js +0 -394
  513. package/dist/tools/automationGenerate.js.map +0 -1
  514. package/dist/tools/capabilities.js +0 -209
  515. package/dist/tools/capabilities.js.map +0 -1
  516. package/dist/tools/detectContext.js +0 -41
  517. package/dist/tools/detectContext.js.map +0 -1
  518. package/dist/tools/flowGenerate.js +0 -55
  519. package/dist/tools/flowGenerate.js.map +0 -1
  520. package/dist/tools/history.js +0 -56
  521. package/dist/tools/history.js.map +0 -1
  522. package/dist/tools/idling.js +0 -104
  523. package/dist/tools/idling.js.map +0 -1
  524. package/dist/tools/inputCapabilities.js +0 -30
  525. package/dist/tools/inputCapabilities.js.map +0 -1
  526. package/dist/tools/locator.js +0 -57
  527. package/dist/tools/locator.js.map +0 -1
  528. package/dist/tools/maestro.js +0 -78
  529. package/dist/tools/maestro.js.map +0 -1
  530. package/dist/tools/permissions.js +0 -149
  531. package/dist/tools/permissions.js.map +0 -1
  532. package/dist/tools/plan.js +0 -49
  533. package/dist/tools/plan.js.map +0 -1
  534. package/dist/tools/screenInfo.js +0 -70
  535. package/dist/tools/screenInfo.js.map +0 -1
  536. package/dist/tools/seed.js +0 -101
  537. package/dist/tools/seed.js.map +0 -1
  538. package/dist/tools/state.js +0 -170
  539. package/dist/tools/state.js.map +0 -1
  540. package/dist/tools/visualText.js +0 -61
  541. package/dist/tools/visualText.js.map +0 -1
@@ -1,12 +1,13 @@
1
1
  // Guided exploration runner (Phase 3.3 Milestones D/E/G). Bounded, safe-by-default screen crawl:
2
- // observe → signature → rank safe candidates → act → health-check → record edge, until a budget is
2
+ // observe > signature > rank safe candidates > act > health-check > record edge, until a budget is
3
3
  // reached. Visual-only screens (poor UI tree) are verified with a screenshot + visual assertion
4
4
  // instead of blind coordinate crawling. Honest: unsafe actions are skipped with a note; an auth
5
- // wall with no credentials returns NeedsInput. Drives only the Driver interface — no MCP-over-MCP.
5
+ // wall with no credentials returns NeedsInput. Drives only the Driver interface, no MCP-over-MCP.
6
6
  import { parseSnapshot } from '../snapshot/parse.js';
7
7
  import { settle } from '../snapshot/settle.js';
8
8
  import { checkHealth } from '../oracle/health.js';
9
9
  import { recordHealthFindings } from '../oracle/record.js';
10
+ import { CancelledError, isAbortError } from '../lib/abortScope.js';
10
11
  import { ExploreGraph } from './graph.js';
11
12
  import { structuredSignature, visualSignature } from './signatures.js';
12
13
  import { actionLikeNonInteractive, rankCandidates, locatorQuality } from './candidates.js';
@@ -55,7 +56,7 @@ function valueForField(session, label, locatorValue, role, secure, gen) {
55
56
  const resolved = resolveFixtureValue(session, label, locatorValue, { role });
56
57
  if (resolved)
57
58
  return resolved;
58
- // Built-in safe generator — only when generation is explicitly allowed for this environment.
59
+ // Built-in safe generator, only when generation is explicitly allowed for this environment.
59
60
  if (gen?.allowed) {
60
61
  const field = fieldKindFromHints(label, locatorValue, undefined, secure);
61
62
  const g = generateFieldValue(field, gen.policy);
@@ -64,7 +65,15 @@ function valueForField(session, label, locatorValue, role, secure, gen) {
64
65
  session.inputValues.set(varName, g.value);
65
66
  if (g.secret)
66
67
  session.secrets.add(g.value);
67
- session.generatedValues.push({ at: Date.now(), fixture: 'generated', field, varName, generator: g.generator, value: g.value, secret: g.secret });
68
+ session.generatedValues.push({
69
+ at: Date.now(),
70
+ fixture: 'generated',
71
+ field,
72
+ varName,
73
+ generator: g.generator,
74
+ value: g.value,
75
+ secret: g.secret,
76
+ });
68
77
  return { value: g.value, varName, secret: g.secret, fixture: 'generated', field, source: 'generator' };
69
78
  }
70
79
  }
@@ -75,7 +84,7 @@ function strategyToKind(strategy) {
75
84
  case 'accessibility':
76
85
  return 'accessibility_id';
77
86
  case 'id':
78
- return 'resource_id'; // Android resource-id → durable id= selector (not a text match)
87
+ return 'resource_id'; // Android resource-id > durable id= selector (not a text match)
79
88
  case 'text':
80
89
  return 'text';
81
90
  default:
@@ -122,10 +131,13 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
122
131
  const maxScreens = opts.maxScreens ?? 12;
123
132
  const depth = opts.depth ?? 3;
124
133
  const stopOnAuth = opts.stopOnAuth ?? true;
125
- // SWIPIUM-REQ-02: resolve the generated-data decision once. Only "allowed" when policy + the
134
+ // Resolve the generated-data decision once. Only "allowed" when policy + the
126
135
  // classified environment (test/staging) permit safe throwaway data; otherwise generation is off.
127
136
  const genCtx = opts.allowGeneratedData
128
- ? (() => { const r = resolveFirstRunPolicy(session, { testDataPolicyPath: opts.testDataPolicyPath, allowGeneratedAccount: true }); return { allowed: r.decision.allowed, policy: r.policy }; })()
137
+ ? (() => {
138
+ const r = resolveFirstRunPolicy(session, { testDataPolicyPath: opts.testDataPolicyPath, allowGeneratedAccount: true });
139
+ return { allowed: r.decision.allowed, policy: r.policy };
140
+ })()
129
141
  : undefined;
130
142
  const startedAt = Date.now();
131
143
  const deadline = startedAt + (opts.maxDurationMs ?? 6 * 60_000);
@@ -144,7 +156,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
144
156
  promotedSuiteCandidates: 0,
145
157
  testabilityBlockers: [],
146
158
  };
147
- const exploredPerScreen = new Map(); // nodeId → explored signatureKeys
159
+ const exploredPerScreen = new Map(); // nodeId > explored signatureKeys
148
160
  const skippedNotedScreens = new Set();
149
161
  const testabilityNotedScreens = new Set();
150
162
  const destructiveCandidates = new Map();
@@ -153,7 +165,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
153
165
  let sameScreenEdges = 0;
154
166
  let planned = false;
155
167
  const note = (n) => sessions.addNote(session, { at: Date.now(), ...n });
156
- /** Observe the current screen → a graph node (structured or visual-only). */
168
+ /** Observe the current screen as a graph node (structured or visual-only). */
157
169
  const observe = async () => {
158
170
  const foreground = await driver.foregroundOwner().catch(() => 'unknown');
159
171
  let xml = '';
@@ -165,14 +177,24 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
165
177
  elements = parsed.elements;
166
178
  webviewDominance = parsed.quality.signals.webviewDominance;
167
179
  }
168
- catch {
169
- /* snapshot failed → treated as visual-only below */
180
+ catch (e) {
181
+ // Cancelled job: an aborted dump is not a visual-only screen, a finding or a blocker.
182
+ if (isAbortError(e, signal))
183
+ throw new CancelledError('exploration cancelled');
184
+ /* snapshot failed, so treated as visual-only below */
170
185
  }
186
+ if (signal?.aborted)
187
+ throw new CancelledError('exploration cancelled');
171
188
  const health = await checkHealth(driver, session.appId, xml || undefined);
189
+ if (health.cancelled || signal?.aborted)
190
+ throw new CancelledError('exploration cancelled');
172
191
  await recordHealthFindings(sessions, session, health.findings, driver, health.foreground);
173
192
  if (!health.nativeHealthy)
174
193
  summary.appErrors += 0; // native handled separately below
175
- const healthSnap = { native: (health.nativeHealthy ? 'OK' : 'error'), app: health.appStatus === 'error' ? 'error' : health.appStatus === 'degraded' ? 'degraded' : 'OK' };
194
+ const healthSnap = {
195
+ native: (health.nativeHealthy ? 'OK' : 'error'),
196
+ app: health.appStatus === 'error' ? 'error' : health.appStatus === 'degraded' ? 'degraded' : 'OK',
197
+ };
176
198
  let screenshotUri;
177
199
  let png;
178
200
  // Respect the SESSION screenshot budget (not a separate fixed counter) so two observes per
@@ -187,7 +209,7 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
187
209
  /* best-effort */
188
210
  }
189
211
  }
190
- // Visual-only (Milestone E): no usable structured elements (canvas/map/webview/empty) → visual
212
+ // Visual-only (Milestone E): no usable structured elements (canvas/map/webview/empty) means visual
191
213
  // node + assertion, no blind coordinate crawl. A low automation-readiness verdict on a screen
192
214
  // that DOES expose identified elements is still structured (we can act on it).
193
215
  const visual = elements.length === 0 || webviewDominance > 0.5;
@@ -216,7 +238,20 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
216
238
  authState: authWall ? 'auth_required' : undefined,
217
239
  visualOnlyReason: visual ? 'no reliable UI tree (canvas/map/webview or empty snapshot)' : undefined,
218
240
  locatorQuality: visual ? undefined : locatorQuality(elements),
219
- elements: candidates.map((c) => ({ ref: c.ref, candidateSignature: c.signatureKey, label: c.label, role: c.role, bounds: c.bounds, locator: c.locator, actionType: c.actionType, risk: c.risk, riskClass: c.riskClass, stepUp: c.stepUp, requiresTwoStepConfirmation: c.requiresTwoStepConfirmation, reason: c.reason })),
241
+ elements: candidates.map((c) => ({
242
+ ref: c.ref,
243
+ candidateSignature: c.signatureKey,
244
+ label: c.label,
245
+ role: c.role,
246
+ bounds: c.bounds,
247
+ locator: c.locator,
248
+ actionType: c.actionType,
249
+ risk: c.risk,
250
+ riskClass: c.riskClass,
251
+ stepUp: c.stepUp,
252
+ requiresTwoStepConfirmation: c.requiresTwoStepConfirmation,
253
+ reason: c.reason,
254
+ })),
220
255
  });
221
256
  if (isNew)
222
257
  summary.screensVisited++;
@@ -226,14 +261,22 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
226
261
  summary.appErrors++;
227
262
  // Visual-only screens get a recorded visual assertion as their oracle (Milestone E).
228
263
  if (visual && isNew) {
229
- note({ workflow: 'guided_exploration', outcome: 'pass', category: undefined, reason: `visual-only screen "${foreground}" — verified by screenshot`, verifiedVisually: true, method: 'visual', artifactUris: screenshotUri ? [screenshotUri] : undefined });
264
+ note({
265
+ workflow: 'guided_exploration',
266
+ outcome: 'pass',
267
+ category: undefined,
268
+ reason: `visual-only screen "${foreground}", verified by screenshot`,
269
+ verifiedVisually: true,
270
+ method: 'visual',
271
+ artifactUris: screenshotUri ? [screenshotUri] : undefined,
272
+ });
230
273
  }
231
274
  return { node, isNew, candidates, visual, authWall, actionLikeSkipped };
232
275
  };
233
276
  const allowedCandidate = (candidate, node) => {
234
277
  if (candidate.risk !== 'destructive')
235
278
  return allowedUnder(candidate.risk, safeMode);
236
- // Controlled account-cycle exception (REQ-07): logout — and only logout — is an expected step
279
+ // Controlled account-cycle exception: logout (and only logout) is an expected step
237
280
  // on a disposable generated account inside the mobile-audit account-cycle workflow.
238
281
  if (opts.accountCycle?.enabled &&
239
282
  allowsControlledLogout({ label: candidate.label, id: candidate.locator?.value, role: candidate.locator?.strategy }, { accountCycle: true, disposableAccount: opts.accountCycle.disposableAccount })) {
@@ -272,155 +315,264 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
272
315
  };
273
316
  // ---- main loop ----
274
317
  let stoppedReason = 'exploration complete';
275
- while (true) {
276
- if (signal?.aborted) {
277
- stoppedReason = 'cancelled';
278
- break;
279
- }
280
- if (summary.actionsTried >= maxActions) {
281
- stoppedReason = `action budget reached (${maxActions})`;
282
- break;
283
- }
284
- if (graph.nodeCount() >= maxScreens) {
285
- stoppedReason = `screen budget reached (${maxScreens})`;
286
- break;
287
- }
288
- if (Date.now() >= deadline) {
289
- stoppedReason = 'time budget reached';
290
- break;
291
- }
292
- const budgetStop = sessions.budgetStop(session);
293
- if (budgetStop) {
294
- stoppedReason = `session budget: ${budgetStop}`;
295
- break;
296
- }
297
- const obs = await observe();
298
- progress(`exploring: ${summary.screensVisited} screens, ${summary.actionsTried} actions${obs.visual ? ', visual-only' : ''}`);
299
- if (!planned && (opts.strategy === 'task_planner' || opts.strategy === 'hybrid')) {
300
- planned = true;
301
- const screenText = obs.candidates.map((c) => `${c.label ?? ''} ${c.locator?.value ?? ''}`).join(' ');
302
- const domain = inferAppDomain({ packageId: session.appId, goal: opts.goal, screenText, fixtures: session.fixtures.map((f) => f.name) });
303
- const tasks = proposeTasks({ packageId: session.appId, goal: opts.goal, screenText, fixtures: session.fixtures.map((f) => f.name), candidates: obs.candidates });
304
- graph.setTasks(tasks);
305
- graph.setHypotheses([`domain:${domain.domain}`, ...tasks.map((t) => `${t.feature}:${t.title}`)]);
306
- graph.setBlockedPreconditions([...new Set(tasks.flatMap((t) => t.preconditions))]);
307
- }
308
- // App error → record + stop this path (do not hide bugs; do not keep crawling a broken screen).
309
- if (obs.node.health.app === 'error') {
310
- note({ workflow: 'guided_exploration', outcome: 'fail', category: 'app_bug', reason: `app-layer error on "${obs.node.title}"`, artifactUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : undefined });
311
- summary.blockers++;
312
- if (lastNodeId)
313
- graph.addEdge({ from: lastNodeId, to: obs.node.id, action: { type: 'tap', targetDescription: 'prev action' }, outcome: 'app_error', evidenceUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : [] });
314
- stoppedReason = 'app-layer error encountered';
315
- break;
316
- }
317
- if (obs.node.health.native === 'error') {
318
- summary.blockers++;
319
- stoppedReason = 'native error encountered';
320
- break;
321
- }
322
- // Auth wall with no credentials.
323
- if (obs.authWall && !hasCredentials(session)) {
324
- note({ workflow: 'guided_exploration', outcome: 'blocked', category: 'missing_test_data', reason: 'login required, no credentials available', missingPrecondition: 'test account credentials', recommendedSetup: 'Provide TEST_EMAIL/TEST_PASSWORD (qa_continue_from_blocker) or accept pre-login coverage.' });
325
- summary.blockers++;
326
- if (stopOnAuth) {
327
- const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, ['auth wall blocked exploration']);
328
- return { graph, summary, needsInput: NeedsInput.credentials('Exploration hit a login screen.'), state: 'needs_input', stoppedReason: 'auth required (credentials missing)', suitePromotion, destructiveCandidates: [...destructiveCandidates.values()] };
318
+ // A cancelled job unwinds via CancelledError (thrown by observe). It's never recorded as a finding,
319
+ // blocker or visual-only screen; the partial graph is still finalized below.
320
+ try {
321
+ while (true) {
322
+ if (signal?.aborted) {
323
+ stoppedReason = 'cancelled';
324
+ break;
329
325
  }
330
- // stopOnAuth:false → record blocked and stop crawling further (only public screens were reachable).
331
- stoppedReason = 'auth required — recorded blocked, no credentials to proceed';
332
- break;
333
- }
334
- const explored = exploredPerScreen.get(obs.node.id) ?? new Set();
335
- exploredPerScreen.set(obs.node.id, explored);
336
- // Visual-only screens: no safe structured target → step back to keep the graph connected.
337
- const pick = obs.candidates.find((c) => !explored.has(c.signatureKey) && allowedCandidate(c, obs.node));
338
- const blockedCandidates = obs.candidates.filter((c) => !allowedCandidate(c, obs.node));
339
- if (blockedCandidates.length && !skippedNotedScreens.has(obs.node.id)) {
340
- skippedNotedScreens.add(obs.node.id);
341
- const ex = blockedCandidates[0];
342
- note({ workflow: 'guided_exploration', outcome: 'skipped', category: ex.risk === 'destructive' ? 'destructive_refused' : 'other', reason: `Skipped ${ex.risk} action "${ex.label ?? ex.locator?.value}": ${ex.reason}`, recommendedSetup: ex.risk === 'destructive' ? 'Run dry_run_destructive to list candidate-bound approvals, then approve one exact candidate with disposable test state.' : 'Add accessibilityLabel/testID so the action can be judged + automated.' });
343
- summary.unsafeActionsSkipped += blockedCandidates.length;
344
- }
345
- if (!pick) {
346
- if (obs.actionLikeSkipped.length && !testabilityNotedScreens.has(obs.node.id)) {
347
- testabilityNotedScreens.add(obs.node.id);
348
- for (const s of obs.actionLikeSkipped.slice(0, 8)) {
349
- summary.testabilityBlockers.push({
350
- screen: obs.node.title ?? 'unknown',
351
- visibleText: s.visibleText,
352
- role: s.role,
353
- bounds: s.bounds,
354
- clickable: s.clickable,
355
- recommendation: `"${s.visibleText}" looks tappable but the UI tree marks it non-interactive. Add accessibilityRole="button" and a stable testID or accessibility identifier.`,
356
- manualCandidate: { x: Math.round(s.bounds.x + s.bounds.w / 2), y: Math.round(s.bounds.y + s.bounds.h / 2) },
326
+ if (summary.actionsTried >= maxActions) {
327
+ stoppedReason = `action budget reached (${maxActions})`;
328
+ break;
329
+ }
330
+ if (graph.nodeCount() >= maxScreens) {
331
+ stoppedReason = `screen budget reached (${maxScreens})`;
332
+ break;
333
+ }
334
+ if (Date.now() >= deadline) {
335
+ stoppedReason = 'time budget reached';
336
+ break;
337
+ }
338
+ const budgetStop = sessions.budgetStop(session);
339
+ if (budgetStop) {
340
+ stoppedReason = `session budget: ${budgetStop}`;
341
+ break;
342
+ }
343
+ const obs = await observe();
344
+ progress(`exploring: ${summary.screensVisited} screens, ${summary.actionsTried} actions${obs.visual ? ', visual-only' : ''}`);
345
+ if (!planned && (opts.strategy === 'task_planner' || opts.strategy === 'hybrid')) {
346
+ planned = true;
347
+ const screenText = obs.candidates.map((c) => `${c.label ?? ''} ${c.locator?.value ?? ''}`).join(' ');
348
+ const domain = inferAppDomain({
349
+ packageId: session.appId,
350
+ goal: opts.goal,
351
+ screenText,
352
+ fixtures: session.fixtures.map((f) => f.name),
353
+ });
354
+ const tasks = proposeTasks({
355
+ packageId: session.appId,
356
+ goal: opts.goal,
357
+ screenText,
358
+ fixtures: session.fixtures.map((f) => f.name),
359
+ candidates: obs.candidates,
360
+ });
361
+ graph.setTasks(tasks);
362
+ graph.setHypotheses([`domain:${domain.domain}`, ...tasks.map((t) => `${t.feature}:${t.title}`)]);
363
+ graph.setBlockedPreconditions([...new Set(tasks.flatMap((t) => t.preconditions))]);
364
+ }
365
+ // App error: record + stop this path (do not hide bugs; do not keep crawling a broken screen).
366
+ if (obs.node.health.app === 'error') {
367
+ note({
368
+ workflow: 'guided_exploration',
369
+ outcome: 'fail',
370
+ category: 'app_bug',
371
+ reason: `app-layer error on "${obs.node.title}"`,
372
+ artifactUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : undefined,
373
+ });
374
+ summary.blockers++;
375
+ if (lastNodeId)
376
+ graph.addEdge({
377
+ from: lastNodeId,
378
+ to: obs.node.id,
379
+ action: { type: 'tap', targetDescription: 'prev action' },
380
+ outcome: 'app_error',
381
+ evidenceUris: obs.node.screenshotUri ? [obs.node.screenshotUri] : [],
357
382
  });
358
- }
383
+ stoppedReason = 'app-layer error encountered';
384
+ break;
385
+ }
386
+ if (obs.node.health.native === 'error') {
387
+ summary.blockers++;
388
+ stoppedReason = 'native error encountered';
389
+ break;
390
+ }
391
+ // Auth wall with no credentials.
392
+ if (obs.authWall && !hasCredentials(session)) {
359
393
  note({
360
394
  workflow: 'guided_exploration',
361
395
  outcome: 'blocked',
362
- category: 'mcp_limitation',
363
- reason: `No safe actionable elements on "${obs.node.title}". ${obs.actionLikeSkipped.length} visible action-like text element(s) are not clickable in the UI tree.`,
364
- missingPrecondition: 'tappable controls with accessibilityRole and testID',
365
- recommendedSetup: `Add accessibilityRole="button" and a stable testID to primary buttons such as "${obs.actionLikeSkipped[0].visibleText}".`,
396
+ category: 'missing_test_data',
397
+ reason: 'login required, no credentials available',
398
+ missingPrecondition: 'test account credentials',
399
+ recommendedSetup: 'Provide SWIPIUM_TEST_EMAIL/SWIPIUM_TEST_PASSWORD (qa_continue_from_blocker) or accept pre-login coverage.',
366
400
  });
401
+ summary.blockers++;
402
+ if (stopOnAuth) {
403
+ const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, ['auth wall blocked exploration']);
404
+ return {
405
+ graph,
406
+ summary,
407
+ needsInput: NeedsInput.credentials('Exploration hit a login screen.'),
408
+ state: 'needs_input',
409
+ stoppedReason: 'auth required (credentials missing)',
410
+ suitePromotion,
411
+ destructiveCandidates: [...destructiveCandidates.values()],
412
+ };
413
+ }
414
+ // stopOnAuth:false: record blocked and stop crawling further (only public screens were reachable).
415
+ stoppedReason = 'auth required: recorded blocked, no credentials to proceed';
416
+ break;
367
417
  }
368
- // Nothing safe+new here. Go back to keep exploring; if we're at the root with nothing, stop.
369
- if (lastNodeId && obs.node.id !== graph.rootId && depth > 1) {
370
- await driver.pressKey('back').catch(() => { });
371
- graph.addEdge({ from: obs.node.id, action: { type: 'back', targetDescription: 'back' }, outcome: 'changed_screen', evidenceUris: [] });
372
- await settle(driver, { timeoutMs: 4000 }).catch(() => { });
373
- lastNodeId = obs.node.id;
374
- continue;
418
+ const explored = exploredPerScreen.get(obs.node.id) ?? new Set();
419
+ exploredPerScreen.set(obs.node.id, explored);
420
+ // Visual-only screens: no safe structured target, so step back to keep the graph connected.
421
+ const pick = obs.candidates.find((c) => !explored.has(c.signatureKey) && allowedCandidate(c, obs.node));
422
+ const blockedCandidates = obs.candidates.filter((c) => !allowedCandidate(c, obs.node));
423
+ if (blockedCandidates.length && !skippedNotedScreens.has(obs.node.id)) {
424
+ skippedNotedScreens.add(obs.node.id);
425
+ const ex = blockedCandidates[0];
426
+ note({
427
+ workflow: 'guided_exploration',
428
+ outcome: 'skipped',
429
+ category: ex.risk === 'destructive' ? 'destructive_refused' : 'other',
430
+ reason: `Skipped ${ex.risk} action "${ex.label ?? ex.locator?.value}": ${ex.reason}`,
431
+ recommendedSetup: ex.risk === 'destructive'
432
+ ? 'Run dry_run_destructive to list candidate-bound approvals, then approve one exact candidate with disposable test state.'
433
+ : 'Add accessibilityLabel/testID so the action can be judged + automated.',
434
+ });
435
+ summary.unsafeActionsSkipped += blockedCandidates.length;
375
436
  }
376
- stoppedReason = 'no further safe, unexplored actions';
377
- break;
378
- }
379
- // ---- act on the chosen candidate ----
380
- explored.add(pick.signatureKey);
381
- const cx = (pick.bounds?.x ?? 0) + (pick.bounds?.w ?? 0) / 2;
382
- const cy = (pick.bounds?.y ?? 0) + (pick.bounds?.h ?? 0) / 2;
383
- const fromId = obs.node.id;
384
- // Text entry only with a value source (P1 fix): never type blindly. If none, skip honestly.
385
- if (pick.actionType === 'type') {
386
- const v = valueForField(session, pick.label, pick.locator?.value, pick.role, pick.secure, genCtx);
387
- if (!v) {
388
- note({ workflow: 'guided_exploration', outcome: 'skipped', category: 'missing_test_data', reason: `text field "${pick.label ?? pick.locator?.value}" needs a value to exercise`, missingPrecondition: 'a fixture value or credential for this field', recommendedSetup: 'Provide credentials (qa_continue_from_blocker) or a fixture { name, value }.' });
389
- summary.blockers++;
437
+ if (!pick) {
438
+ if (obs.actionLikeSkipped.length && !testabilityNotedScreens.has(obs.node.id)) {
439
+ testabilityNotedScreens.add(obs.node.id);
440
+ for (const s of obs.actionLikeSkipped.slice(0, 8)) {
441
+ summary.testabilityBlockers.push({
442
+ screen: obs.node.title ?? 'unknown',
443
+ visibleText: s.visibleText,
444
+ role: s.role,
445
+ bounds: s.bounds,
446
+ clickable: s.clickable,
447
+ recommendation: `"${s.visibleText}" looks tappable but the UI tree marks it non-interactive. Add accessibilityRole="button" and a stable testID or accessibility identifier.`,
448
+ manualCandidate: { x: Math.round(s.bounds.x + s.bounds.w / 2), y: Math.round(s.bounds.y + s.bounds.h / 2) },
449
+ });
450
+ }
451
+ note({
452
+ workflow: 'guided_exploration',
453
+ outcome: 'blocked',
454
+ category: 'mcp_limitation',
455
+ reason: `No safe actionable elements on "${obs.node.title}". ${obs.actionLikeSkipped.length} visible action-like text element(s) are not clickable in the UI tree.`,
456
+ missingPrecondition: 'tappable controls with accessibilityRole and testID',
457
+ recommendedSetup: `Add accessibilityRole="button" and a stable testID to primary buttons such as "${obs.actionLikeSkipped[0].visibleText}".`,
458
+ });
459
+ }
460
+ // Nothing safe+new here. Go back to keep exploring; if we're at the root with nothing, stop.
461
+ if (lastNodeId && obs.node.id !== graph.rootId && depth > 1) {
462
+ await driver.pressKey('back').catch(() => { });
463
+ graph.addEdge({
464
+ from: obs.node.id,
465
+ action: { type: 'back', targetDescription: 'back' },
466
+ outcome: 'changed_screen',
467
+ evidenceUris: [],
468
+ });
469
+ await settle(driver, { timeoutMs: 4000 }).catch(() => { });
470
+ lastNodeId = obs.node.id;
471
+ continue;
472
+ }
473
+ stoppedReason = 'no further safe, unexplored actions';
474
+ break;
475
+ }
476
+ // ---- act on the chosen candidate ----
477
+ explored.add(pick.signatureKey);
478
+ const cx = (pick.bounds?.x ?? 0) + (pick.bounds?.w ?? 0) / 2;
479
+ const cy = (pick.bounds?.y ?? 0) + (pick.bounds?.h ?? 0) / 2;
480
+ const fromId = obs.node.id;
481
+ // Text entry only with a value source (P1 fix): never type blindly. If none, skip honestly.
482
+ if (pick.actionType === 'type') {
483
+ const v = valueForField(session, pick.label, pick.locator?.value, pick.role, pick.secure, genCtx);
484
+ if (!v) {
485
+ note({
486
+ workflow: 'guided_exploration',
487
+ outcome: 'skipped',
488
+ category: 'missing_test_data',
489
+ reason: `text field "${pick.label ?? pick.locator?.value}" needs a value to exercise`,
490
+ missingPrecondition: 'a fixture value or credential for this field',
491
+ recommendedSetup: 'Provide credentials (qa_continue_from_blocker) or a fixture { name, value }.',
492
+ });
493
+ summary.blockers++;
494
+ continue;
495
+ }
496
+ try {
497
+ await driver.tapXY(Math.round(cx), Math.round(cy)); // focus the field
498
+ await driver.inputText(v.value);
499
+ }
500
+ catch {
501
+ graph.addEdge({
502
+ from: fromId,
503
+ action: { type: 'type', targetDescription: pick.label ?? 'field' },
504
+ outcome: 'blocked',
505
+ evidenceUris: [],
506
+ });
507
+ continue;
508
+ }
509
+ summary.actionsTried++;
510
+ sessions.bump(session, 'actions');
511
+ if (v.secret)
512
+ session.secrets.add(v.value);
513
+ if (v.source === 'generator') {
514
+ const rec = session.generatedValues.find((g) => g.varName === v.varName && g.fixture === v.fixture && g.field === v.field);
515
+ if (rec && !rec.artifactUri) {
516
+ rec.artifactUri = sessions.saveArtifact(session, 'generated_data', `generated-${v.varName.toLowerCase()}-${Date.now()}.json`, JSON.stringify({
517
+ schema: 'swipium.generated_value.v1',
518
+ fixture: rec.fixture,
519
+ field: rec.field,
520
+ varName: rec.varName,
521
+ generator: rec.generator,
522
+ value: rec.secret ? '<redacted>' : rec.value,
523
+ secret: rec.secret,
524
+ }, null, 2), 'application/json', `generated fixture value ${rec.fixture}.${rec.field}`);
525
+ sessions.persist(session);
526
+ }
527
+ }
528
+ if (pick.locator && pick.locator.strategy !== 'coordinate') {
529
+ sessions.addRecordedAction(session, {
530
+ at: Date.now(),
531
+ action: 'type',
532
+ selector: pick.locator.value,
533
+ selectorKind: strategyToKind(pick.locator.strategy),
534
+ text: `\${${v.varName}}`,
535
+ secret: v.secret,
536
+ exportability: v.secret ? 'needs-human-data' : 'semantic',
537
+ screen: obs.node.title,
538
+ provenance: provenanceForCandidate(obs.node, pick),
539
+ });
540
+ }
541
+ await settle(driver, { timeoutMs: 5000 }).catch(() => { });
542
+ const afterType = await observe();
543
+ graph.addEdge({
544
+ from: fromId,
545
+ to: afterType.node.id,
546
+ action: { type: 'type', targetDescription: pick.label ?? 'field' },
547
+ outcome: afterType.node.id === fromId ? 'same_screen' : 'changed_screen',
548
+ evidenceUris: [],
549
+ });
550
+ lastNodeId = afterType.node.id;
390
551
  continue;
391
552
  }
392
553
  try {
393
- await driver.tapXY(Math.round(cx), Math.round(cy)); // focus the field
394
- await driver.inputText(v.value);
554
+ await driver.tapXY(Math.round(cx), Math.round(cy));
395
555
  }
396
556
  catch {
397
- graph.addEdge({ from: fromId, action: { type: 'type', targetDescription: pick.label ?? 'field' }, outcome: 'blocked', evidenceUris: [] });
398
- continue;
399
- }
400
- summary.actionsTried++;
401
- sessions.bump(session, 'actions');
402
- if (v.secret)
403
- session.secrets.add(v.value);
404
- if (v.source === 'generator') {
405
- const rec = session.generatedValues.find((g) => g.varName === v.varName && g.fixture === v.fixture && g.field === v.field);
406
- if (rec && !rec.artifactUri) {
407
- rec.artifactUri = sessions.saveArtifact(session, 'generated_data', `generated-${v.varName.toLowerCase()}-${Date.now()}.json`, JSON.stringify({ schema: 'swipium.generated_value.v1', fixture: rec.fixture, field: rec.field, varName: rec.varName, generator: rec.generator, value: rec.secret ? '<redacted>' : rec.value, secret: rec.secret }, null, 2), 'application/json', `generated fixture value ${rec.fixture}.${rec.field}`);
408
- sessions.persist(session);
557
+ if (pick.risk === 'destructive') {
558
+ sessions.recordMutation(session, {
559
+ tool: 'qa_explore',
560
+ action: 'destructive_ui_candidate',
561
+ risk: 'high',
562
+ target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
563
+ consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
564
+ status: 'blocked',
565
+ detail: 'Approved destructive exploration candidate could not be tapped.',
566
+ });
409
567
  }
568
+ graph.addEdge({
569
+ from: fromId,
570
+ action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control' },
571
+ outcome: 'blocked',
572
+ evidenceUris: [],
573
+ });
574
+ continue;
410
575
  }
411
- if (pick.locator && pick.locator.strategy !== 'coordinate') {
412
- sessions.addRecordedAction(session, { at: Date.now(), action: 'type', selector: pick.locator.value, selectorKind: strategyToKind(pick.locator.strategy), text: `\${${v.varName}}`, secret: v.secret, exportability: v.secret ? 'needs-human-data' : 'semantic', screen: obs.node.title, provenance: provenanceForCandidate(obs.node, pick) });
413
- }
414
- await settle(driver, { timeoutMs: 5000 }).catch(() => { });
415
- const afterType = await observe();
416
- graph.addEdge({ from: fromId, to: afterType.node.id, action: { type: 'type', targetDescription: pick.label ?? 'field' }, outcome: afterType.node.id === fromId ? 'same_screen' : 'changed_screen', evidenceUris: [] });
417
- lastNodeId = afterType.node.id;
418
- continue;
419
- }
420
- try {
421
- await driver.tapXY(Math.round(cx), Math.round(cy));
422
- }
423
- catch {
424
576
  if (pick.risk === 'destructive') {
425
577
  sessions.recordMutation(session, {
426
578
  tool: 'qa_explore',
@@ -428,52 +580,73 @@ export async function runExplore(sessions, session, driver, opts, ctx = {}) {
428
580
  risk: 'high',
429
581
  target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
430
582
  consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
431
- status: 'blocked',
432
- detail: 'Approved destructive exploration candidate could not be tapped.',
583
+ status: 'executed',
584
+ detail: 'Approved destructive exploration candidate tapped.',
433
585
  });
434
586
  }
435
- graph.addEdge({ from: fromId, action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control' }, outcome: 'blocked', evidenceUris: [] });
436
- continue;
437
- }
438
- if (pick.risk === 'destructive') {
439
- sessions.recordMutation(session, {
440
- tool: 'qa_explore',
441
- action: 'destructive_ui_candidate',
442
- risk: 'high',
443
- target: destructiveMutationTarget(session, obs.node, pick, opts.destructiveApproval),
444
- consent: { required: true, consentId: opts.destructiveApproval?.consentId, approved: true },
445
- status: 'executed',
446
- detail: 'Approved destructive exploration candidate tapped.',
587
+ summary.actionsTried++;
588
+ sessions.bump(session, 'actions');
589
+ // Record durable taps so qa_generate target:"suite" can promote the path (§9.3).
590
+ if (pick.locator && pick.locator.strategy !== 'coordinate') {
591
+ sessions.addRecordedAction(session, {
592
+ at: Date.now(),
593
+ action: 'tap',
594
+ selector: pick.locator.value,
595
+ selectorKind: strategyToKind(pick.locator.strategy),
596
+ exportability: 'semantic',
597
+ screen: obs.node.title,
598
+ provenance: provenanceForCandidate(obs.node, pick),
599
+ });
600
+ }
601
+ await settle(driver, { timeoutMs: 5000 }).catch(() => { });
602
+ // ---- observe outcome to classify the transition ----
603
+ const after = await observe();
604
+ const outcome = after.node.id === fromId ? 'same_screen' : 'changed_screen';
605
+ if (outcome === 'changed_screen') {
606
+ summary.workflowsFound++;
607
+ consecutiveNoChange = 0;
608
+ }
609
+ else {
610
+ consecutiveNoChange++;
611
+ }
612
+ if (outcome === 'same_screen')
613
+ sameScreenEdges++;
614
+ graph.addEdge({
615
+ from: fromId,
616
+ to: after.node.id,
617
+ action: {
618
+ type: 'tap',
619
+ targetDescription: pick.label ?? pick.locator?.value ?? 'control',
620
+ locator: pick.locator,
621
+ },
622
+ outcome,
623
+ evidenceUris: after.node.screenshotUri ? [after.node.screenshotUri] : [],
624
+ riskDecision: pick.riskClass ?? pick.risk,
625
+ preActionState: obs.node.signature,
626
+ postActionState: after.node.signature,
627
+ oracle: outcome === 'changed_screen' ? 'screen_signature_changed' : 'screen_signature_same',
447
628
  });
629
+ lastNodeId = after.node.id;
630
+ if (consecutiveNoChange >= 3) {
631
+ stoppedReason = 'repeated no-change actions';
632
+ break;
633
+ }
448
634
  }
449
- summary.actionsTried++;
450
- sessions.bump(session, 'actions');
451
- // Record durable taps so qa_suite_generate can promote the path (§9.3).
452
- if (pick.locator && pick.locator.strategy !== 'coordinate') {
453
- sessions.addRecordedAction(session, { at: Date.now(), action: 'tap', selector: pick.locator.value, selectorKind: strategyToKind(pick.locator.strategy), exportability: 'semantic', screen: obs.node.title, provenance: provenanceForCandidate(obs.node, pick) });
454
- }
455
- await settle(driver, { timeoutMs: 5000 }).catch(() => { });
456
- // ---- observe outcome to classify the transition ----
457
- const after = await observe();
458
- const outcome = after.node.id === fromId ? 'same_screen' : 'changed_screen';
459
- if (outcome === 'changed_screen') {
460
- summary.workflowsFound++;
461
- consecutiveNoChange = 0;
462
- }
463
- else {
464
- consecutiveNoChange++;
465
- }
466
- if (outcome === 'same_screen')
467
- sameScreenEdges++;
468
- graph.addEdge({ from: fromId, to: after.node.id, action: { type: 'tap', targetDescription: pick.label ?? pick.locator?.value ?? 'control', locator: pick.locator }, outcome, evidenceUris: after.node.screenshotUri ? [after.node.screenshotUri] : [], riskDecision: pick.riskClass ?? pick.risk, preActionState: obs.node.signature, postActionState: after.node.signature, oracle: outcome === 'changed_screen' ? 'screen_signature_changed' : 'screen_signature_same' });
469
- lastNodeId = after.node.id;
470
- if (consecutiveNoChange >= 3) {
471
- stoppedReason = 'repeated no-change actions';
472
- break;
473
- }
635
+ }
636
+ catch (e) {
637
+ if (!(e instanceof CancelledError) && !(signal?.aborted && isAbortError(e, signal)))
638
+ throw e;
639
+ stoppedReason = 'cancelled';
474
640
  }
475
641
  const suitePromotion = finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, []);
476
- return { graph, summary, state: summary.appErrors > 0 || summary.blockers > 0 ? 'blocked' : 'completed', stoppedReason, suitePromotion, destructiveCandidates: [...destructiveCandidates.values()] };
642
+ return {
643
+ graph,
644
+ summary,
645
+ state: summary.appErrors > 0 || summary.blockers > 0 ? 'blocked' : 'completed',
646
+ stoppedReason,
647
+ suitePromotion,
648
+ destructiveCandidates: [...destructiveCandidates.values()],
649
+ };
477
650
  }
478
651
  function finalizeGraph(graph, summary, sameScreenEdges, destructiveCandidates, reasons) {
479
652
  const nodes = graph.allNodes();