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,13 +1,63 @@
1
+ import { currentScreenSizeEpoch } from './DirectDriver.js';
1
2
  import * as sim from '../lib/simctl.js';
2
- import { acceptWdaAlert, clearWdaElement, clearWdaFocusedByKeys, createWdaSession, deleteWdaSession, dismissWdaAlert, dragWdaPoint, findFocusedWdaElement, findWdaElement, normalizeWdaSource, pressWdaBack, pressWdaHome, tapWdaElement, tapWdaPoint, typeWdaElement, typeWdaKeys, wdaActiveAppInfo, wdaScreenshot, wdaSessionUdidMismatch, wdaSource, } from '../lib/wda.js';
3
+ import { currentSignal } from '../lib/abortScope.js';
4
+ import { parseSnapshot } from '../snapshot/parse.js';
5
+ import { acceptWdaAlert, clearWdaElement, clearWdaFocusedByKeys, createWdaSession, deleteWdaSession, dismissWdaAlert, dismissWdaKeyboard, isInvalidWdaSession, wdaKeyboardFrame, wdaKeyboardShown, wdaOrientation, withWdaCall, dragWdaPoint, findFocusedWdaElement, findWdaElement, normalizeWdaSource, pressWdaHome, tapWdaElement, tapWdaPoint, typeWdaElement, typeWdaKeys, wdaActiveAppInfo, wdaElementAttribute, wdaScreenshot, wdaSessionUdidMismatch, wdaSource, wdaWindowSize, } from '../lib/wda.js';
3
6
  const UNSUPPORTED = 'not supported by the WDA backend yet.';
7
+ /** A cached screen size is re-validated (GET /orientation + /window/size) after this long even
8
+ * without an observed rotation. Rotations seen in a page source drop it immediately. */
9
+ export const WDA_SCREEN_SIZE_TTL_MS = 30_000;
10
+ /** How long a post-settle page source may answer `press back` (only while no action ran since and
11
+ * nothing invalidated the device's screen). Short on purpose: the app can navigate by itself. */
12
+ export const WDA_BACK_SOURCE_FRESH_MS = 3_000;
13
+ /** Per-device page-source epoch: bumped by any out-of-driver screen change on that simulator
14
+ * (simctl launch/terminate/openurl, app-control tools, flow openUrl) so a cached source taken
15
+ * before it is never reused to find a back button. Keyed by udid; '*' = every device. */
16
+ const pageSourceEpoch = new Map();
17
+ function sourceEpochOf(udid) {
18
+ return (pageSourceEpoch.get(udid ?? '') ?? 0) + (pageSourceEpoch.get('*') ?? 0);
19
+ }
20
+ /** Drop any WdaDriver's cached page source for `udid` (all devices when omitted). Call after any
21
+ * simctl / app-control action that can change the screen outside the WDA driver. */
22
+ export function invalidateWdaPageSource(udid) {
23
+ const key = udid ?? '*';
24
+ pageSourceEpoch.set(key, (pageSourceEpoch.get(key) ?? 0) + 1);
25
+ }
26
+ /** How long the element id found by clearFocusedText() is reused by the following inputText(). */
27
+ export const WDA_FOCUSED_REUSE_MS = 10_000;
4
28
  function tailLines(text, lines) {
5
29
  const all = text.split(/\r?\n/);
6
30
  return all.slice(Math.max(0, all.length - lines)).join('\n');
7
31
  }
8
32
  function errorSummary(e) {
9
- return String(e?.message ?? e).replace(/\s+/g, ' ').slice(0, 300);
33
+ return String(e?.message ?? e)
34
+ .replace(/\s+/g, ' ')
35
+ .slice(0, 300);
36
+ }
37
+ /** PURE: where iOS "back" is on this screen: the center of the navigation bar's back button in a
38
+ * (normalized) WDA source, or null. The back button is the FIRST XCUIElementTypeButton inside the
39
+ * first XCUIElementTypeNavigationBar, and only when it sits in the bar's LEFT half. A root screen's
40
+ * bar can hold only trailing buttons ("Edit", "+"), which are not back. */
41
+ export function iosBackButtonPoint(normalizedXml) {
42
+ const nodes = parseSnapshot(normalizedXml).allNodes;
43
+ const bar = nodes.find((n) => /XCUIElementTypeNavigationBar$/.test(n.cls));
44
+ if (!bar)
45
+ return null;
46
+ const btn = nodes.find((n) => n.dfs > bar.dfs &&
47
+ n.dfs <= bar.subtreeEnd &&
48
+ /XCUIElementTypeButton$/.test(n.cls) &&
49
+ n.enabled &&
50
+ n.bounds[2] > n.bounds[0] &&
51
+ n.bounds[3] > n.bounds[1]);
52
+ if (!btn)
53
+ return null;
54
+ const x = Math.round((btn.bounds[0] + btn.bounds[2]) / 2);
55
+ const y = Math.round((btn.bounds[1] + btn.bounds[3]) / 2);
56
+ const barMid = (bar.bounds[0] + bar.bounds[2]) / 2;
57
+ return x < barMid ? { x, y } : null;
10
58
  }
59
+ /** Thrown (message prefix) when iOS has no way to go "back" on the current screen. */
60
+ export const IOS_BACK_UNSUPPORTED = 'BACKEND_UNSUPPORTED: iOS has no system back key. No navigation-bar back button was found and the screen size needed for an edge-swipe back gesture is unknown.';
11
61
  export class WdaDriver {
12
62
  kind = 'wda';
13
63
  baseUrl;
@@ -16,6 +66,8 @@ export class WdaDriver {
16
66
  sessionId;
17
67
  bundleId;
18
68
  capabilities;
69
+ /** Every session this driver creates re-uses the running app (restart rebind). */
70
+ reuseRunningApp;
19
71
  settings;
20
72
  onTiming;
21
73
  constructor(baseUrl, opts = {}) {
@@ -25,6 +77,7 @@ export class WdaDriver {
25
77
  this.sessionId = opts.sessionId;
26
78
  this.bundleId = opts.bundleId;
27
79
  this.capabilities = opts.capabilities;
80
+ this.reuseRunningApp = opts.reuseRunningApp === true;
28
81
  this.settings = opts.settings;
29
82
  this.onTiming = opts.onTiming;
30
83
  }
@@ -37,10 +90,73 @@ export class WdaDriver {
37
90
  this.onTiming?.(kind, Date.now() - started);
38
91
  }
39
92
  }
40
- async ensureSession() {
93
+ /** Last normalized page source from dumpXml(). It's cleared by every state-changing call, so while
94
+ * set it IS the current screen (as of `at`). Lets `press back` skip a fresh /source. */
95
+ lastSource;
96
+ /** Focused element found by clearFocusedText(), reused by the next inputText(). */
97
+ focusedEl;
98
+ /** Called by every state-changing operation. */
99
+ touched() {
100
+ this.lastSource = undefined;
101
+ }
102
+ /** Run a simctl app-control call (launch/terminate/install/openurl): the screen may change
103
+ * outside WDA, so every driver's cached source for this simulator is dropped before AND after
104
+ * (a source dumped while the call ran is stale too). */
105
+ async simctlChange(fn) {
106
+ this.touched();
107
+ invalidateWdaPageSource(this.udid);
108
+ try {
109
+ return await fn();
110
+ }
111
+ finally {
112
+ invalidateWdaPageSource(this.udid);
113
+ }
114
+ }
115
+ /** Set when a session was transparently re-created after "invalid session id" (WDA restart /
116
+ * session reaped). Callers may surface it as a warning via consumeSessionRecovered(). */
117
+ sessionRecovered = false;
118
+ /** Returns (and clears) whether the last operations ran on a re-created WDA session. */
119
+ consumeSessionRecovered() {
120
+ const v = this.sessionRecovered;
121
+ this.sessionRecovered = false;
122
+ return v;
123
+ }
124
+ /** Run a session-scoped WDA operation with the bound signal + per-endpoint timeouts. On
125
+ * "invalid session id" (WDA restarted / session reaped) the cached session is dropped and
126
+ * the operation retried ONCE on a fresh session; the failed request never acted. The fresh
127
+ * session is created with forceAppLaunch:false + shouldTerminateApp:false (reuseRunningApp) so
128
+ * WDA does NOT terminate + relaunch the app under test (WebDriverAgent FBSessionCommands: when
129
+ * `bundleId` is passed, `forceAppLaunch` defaults to YES and a running app is relaunched; with
130
+ * NO a running app is left as-is and a backgrounded one is only activated). */
131
+ async withSession(fn) {
132
+ return withWdaCall({ signal: currentSignal() }, async () => {
133
+ const sid = await this.ensureSession();
134
+ try {
135
+ return await fn(sid);
136
+ }
137
+ catch (e) {
138
+ if (!isInvalidWdaSession(e))
139
+ throw e;
140
+ this.sessionId = undefined;
141
+ this.cachedScreenSize = undefined;
142
+ this.lastSource = undefined;
143
+ this.focusedEl = undefined;
144
+ const fresh = await this.ensureSession({ recover: true });
145
+ this.sessionRecovered = true;
146
+ return fn(fresh);
147
+ }
148
+ });
149
+ }
150
+ async ensureSession(opts = {}) {
41
151
  if (this.sessionId)
42
152
  return this.sessionId;
43
- const s = await this.timed('session_create', () => createWdaSession(this.baseUrl, { bundleId: this.bundleId, udid: this.udid, capabilities: this.capabilities, settings: this.settings }));
153
+ const s = await this.timed('session_create', () => createWdaSession(this.baseUrl, {
154
+ bundleId: this.bundleId,
155
+ udid: this.udid,
156
+ capabilities: this.capabilities,
157
+ settings: this.settings,
158
+ reuseRunningApp: this.reuseRunningApp || opts.recover === true,
159
+ }));
44
160
  const mismatchedUdid = wdaSessionUdidMismatch(s.capabilities, this.udid);
45
161
  if (mismatchedUdid) {
46
162
  throw new Error(`STALE_WDA_DEVICE: WDA session is bound to ${mismatchedUdid}, not requested device ${this.udid}.`);
@@ -73,12 +189,18 @@ export class WdaDriver {
73
189
  async installApp(appPath) {
74
190
  if (!this.udid || !this.simulator.installApp)
75
191
  return this.no('app install');
76
- await this.simulator.installApp(this.udid, appPath);
192
+ const udid = this.udid;
193
+ await this.simctlChange(async () => {
194
+ await this.simulator.installApp?.(udid, appPath);
195
+ });
77
196
  }
78
197
  async uninstallApp(pkg) {
79
198
  if (!this.udid || !this.simulator.uninstallApp)
80
199
  return this.no('app uninstall');
81
- await this.simulator.uninstallApp(this.udid, pkg);
200
+ const udid = this.udid;
201
+ await this.simctlChange(async () => {
202
+ await this.simulator.uninstallApp?.(udid, pkg);
203
+ });
82
204
  }
83
205
  async isInstalled(pkg) {
84
206
  const target = pkg || this.bundleId;
@@ -108,16 +230,22 @@ export class WdaDriver {
108
230
  }
109
231
  }
110
232
  async launchApp(pkg) {
233
+ this.touched();
111
234
  this.bundleId = pkg;
112
- if (this.udid) {
113
- await this.simulator.launchApp(this.udid, pkg);
235
+ const udid = this.udid;
236
+ if (udid) {
237
+ await this.simctlChange(() => this.simulator.launchApp(udid, pkg));
114
238
  }
115
239
  await this.ensureSession();
116
240
  }
117
241
  async launchAppWithArgs(pkg, args) {
242
+ this.touched();
118
243
  this.bundleId = pkg;
119
- if (this.udid && this.simulator.launchAppWithArgs) {
120
- await this.simulator.launchAppWithArgs(this.udid, pkg, args);
244
+ const udid = this.udid;
245
+ if (udid && this.simulator.launchAppWithArgs) {
246
+ await this.simctlChange(async () => {
247
+ await this.simulator.launchAppWithArgs?.(udid, pkg, args);
248
+ });
121
249
  }
122
250
  else if (Object.keys(args).length) {
123
251
  return this.no('launch arguments');
@@ -128,20 +256,52 @@ export class WdaDriver {
128
256
  const target = pkg || this.bundleId;
129
257
  if (!this.udid || !target)
130
258
  return this.no('app terminate');
131
- await this.simulator.terminateApp(this.udid, target);
259
+ const udid = this.udid;
260
+ await this.simctlChange(() => this.simulator.terminateApp(udid, target));
132
261
  }
133
262
  clearData() {
134
263
  return this.no('clear app data');
135
264
  }
136
- imeShown() {
137
- return Promise.resolve(false);
265
+ /** Keyboard shown? A single find-by-class-name call (XCUIElementTypeKeyboard). */
266
+ async imeShown() {
267
+ return this.withSession((sid) => wdaKeyboardShown(this.baseUrl, sid));
268
+ }
269
+ async imeFrame() {
270
+ return this.withSession((sid) => wdaKeyboardFrame(this.baseUrl, sid));
271
+ }
272
+ /** imeShown + imeFrame in ONE keyboard lookup (both used to run the same /elements query):
273
+ * no keyboard element (or no usable rect) ⇒ not shown. */
274
+ async imeState() {
275
+ const frame = await this.imeFrame();
276
+ return { shown: frame !== null, frame };
277
+ }
278
+ async hideKeyboard() {
279
+ this.touched();
280
+ return this.withSession(async (sid) => {
281
+ if (!(await wdaKeyboardShown(this.baseUrl, sid)))
282
+ return false;
283
+ try {
284
+ await dismissWdaKeyboard(this.baseUrl, sid);
285
+ }
286
+ catch (e) {
287
+ if (isInvalidWdaSession(e))
288
+ throw e; // let withSession recover the session
289
+ // WDA 400 "Did not know how to dismiss the keyboard": the app offers no generic way
290
+ // (no Done/Return-dismiss). Report "could not hide" instead of an untyped error.
291
+ return false;
292
+ }
293
+ return true;
294
+ });
138
295
  }
139
296
  async logcat(lines = 200, grep) {
140
297
  if (!this.udid)
141
298
  return this.no('iOS simulator log capture without a simulator UDID');
142
299
  const raw = await this.simulator.simulatorLogs(this.udid, { last: '5m', bundleId: this.bundleId });
143
300
  const filtered = grep
144
- ? raw.split(/\r?\n/).filter((line) => new RegExp(grep, 'i').test(line)).join('\n')
301
+ ? raw
302
+ .split(/\r?\n/)
303
+ .filter((line) => new RegExp(grep, 'i').test(line))
304
+ .join('\n')
145
305
  : raw;
146
306
  return tailLines(filtered, lines);
147
307
  }
@@ -152,32 +312,74 @@ export class WdaDriver {
152
312
  return this.no('airplane-mode toggle');
153
313
  }
154
314
  async foregroundOwner() {
155
- try {
156
- const info = await wdaActiveAppInfo(this.baseUrl, await this.ensureSession());
157
- return info.bundleId ?? info.name ?? this.bundleId ?? 'unknown';
158
- }
159
- catch {
160
- return this.bundleId ?? 'unknown';
315
+ // Never guess the app under test on failure: callers treat 'unknown' as "don't judge", while a
316
+ // guessed bundleId made qa_app_control background report the app as still in front.
317
+ for (let attempt = 0; attempt < 2; attempt++) {
318
+ try {
319
+ const info = await this.withSession((sid) => wdaActiveAppInfo(this.baseUrl, sid));
320
+ return info.bundleId || info.name || 'unknown';
321
+ }
322
+ catch {
323
+ if (attempt === 0)
324
+ await new Promise((r) => setTimeout(r, 300));
325
+ }
161
326
  }
327
+ return 'unknown';
162
328
  }
163
329
  async screenshot() {
164
- const sid = await this.ensureSession();
165
- return this.timed('screenshot', () => wdaScreenshot(this.baseUrl, sid));
166
- }
167
- async dumpXml() {
168
- const sid = await this.ensureSession();
169
- return normalizeWdaSource(await this.timed('source', () => wdaSource(this.baseUrl, sid)));
330
+ return this.withSession((sid) => this.timed('screenshot', () => wdaScreenshot(this.baseUrl, sid)));
331
+ }
332
+ async dumpXml(opts = {}) {
333
+ // opts.timeoutMs bounds the /source request (default 30 s). The settle loop passes its
334
+ // remaining deadline. Retries are the caller's loop; WDA itself is not retried here.
335
+ let sid;
336
+ const xml = normalizeWdaSource(await withWdaCall({ timeoutMs: opts.timeoutMs }, () => this.withSession((s) => {
337
+ sid = s;
338
+ return this.timed('source', () => wdaSource(this.baseUrl, s));
339
+ })));
340
+ this.lastSource = { sid, xml, at: Date.now(), epoch: sourceEpochOf(this.udid) };
341
+ this.noteSourceSize(xml);
342
+ return xml;
343
+ }
344
+ /** A page source whose root size is the cached size with SWAPPED axes = a rotation happened. */
345
+ noteSourceSize(xml) {
346
+ const c = this.cachedScreenSize;
347
+ if (!c)
348
+ return;
349
+ const m = xml.match(/bounds="\[0,0\]\[(\d+),(\d+)\]"/);
350
+ if (!m)
351
+ return;
352
+ const [w, h] = [Number(m[1]), Number(m[2])];
353
+ if (w === c.size.height && h === c.size.width && w !== h)
354
+ this.cachedScreenSize = undefined;
170
355
  }
171
356
  async tapXY(x, y) {
172
- const sid = await this.ensureSession();
173
- await this.timed('tap', () => tapWdaPoint(this.baseUrl, sid, x, y));
357
+ this.touched();
358
+ await this.withSession((sid) => this.timed('tap', () => tapWdaPoint(this.baseUrl, sid, x, y)));
174
359
  }
175
360
  async pressXY(x, y) {
176
361
  await this.tapXY(x, y);
177
362
  }
178
363
  async inputText(text) {
179
- const sid = await this.ensureSession();
364
+ this.touched();
365
+ return this.withSession((sid) => this.inputTextIn(sid, text));
366
+ }
367
+ async inputTextIn(sid, text) {
180
368
  let focusedError;
369
+ // Reuse the element clearFocusedText() just found (saves one find round trip per type);
370
+ // on any failure fall through to a fresh lookup.
371
+ const reuse = this.focusedEl;
372
+ this.focusedEl = undefined;
373
+ if (reuse && reuse.sid === sid && Date.now() - reuse.at < WDA_FOCUSED_REUSE_MS) {
374
+ try {
375
+ await this.timed('type', () => typeWdaElement(this.baseUrl, sid, reuse.elementId, text));
376
+ return;
377
+ }
378
+ catch (e) {
379
+ if (isInvalidWdaSession(e))
380
+ throw e;
381
+ }
382
+ }
181
383
  try {
182
384
  const el = await this.timed('find_element', () => findFocusedWdaElement(this.baseUrl, sid));
183
385
  await this.timed('type', () => typeWdaElement(this.baseUrl, sid, el.elementId, text));
@@ -191,15 +393,20 @@ export class WdaDriver {
191
393
  return;
192
394
  }
193
395
  catch (keysError) {
194
- throw new Error(`TEXT_INPUT_UNSUPPORTED: WDA could not type into the focused input. focused-element path failed: ${errorSummary(focusedError)}; /wda/keys path failed: ${errorSummary(keysError)}`);
396
+ throw new Error(`TEXT_INPUT_UNSUPPORTED: WDA could not type into the focused input. focused-element path failed: ${errorSummary(focusedError)}; /wda/keys path failed: ${errorSummary(keysError)}`, { cause: keysError });
195
397
  }
196
398
  }
197
399
  async clearFocusedText(approxLen = 40) {
198
- const sid = await this.ensureSession();
400
+ this.touched();
401
+ return this.withSession((sid) => this.clearFocusedTextIn(sid, approxLen));
402
+ }
403
+ async clearFocusedTextIn(sid, approxLen) {
199
404
  let clearError;
405
+ this.focusedEl = undefined;
200
406
  try {
201
407
  const el = await this.timed('find_element', () => findFocusedWdaElement(this.baseUrl, sid));
202
408
  await this.timed('clear', () => clearWdaElement(this.baseUrl, sid, el.elementId));
409
+ this.focusedEl = { sid, elementId: el.elementId, at: Date.now() };
203
410
  return;
204
411
  }
205
412
  catch (e) {
@@ -209,36 +416,112 @@ export class WdaDriver {
209
416
  await this.timed('clear', () => clearWdaFocusedByKeys(this.baseUrl, sid, approxLen));
210
417
  }
211
418
  catch (keysError) {
212
- throw new Error(`TEXT_INPUT_UNSUPPORTED: WDA could not clear the focused field. element-clear path failed: ${errorSummary(clearError)}; keyboard-backspace path failed: ${errorSummary(keysError)}`);
419
+ throw new Error(`TEXT_INPUT_UNSUPPORTED: WDA could not clear the focused field. element-clear path failed: ${errorSummary(clearError)}; keyboard-backspace path failed: ${errorSummary(keysError)}`, { cause: keysError });
213
420
  }
214
421
  }
215
422
  async pressKey(key) {
423
+ const cached = this.lastSource; // the screen as last dumped, if nothing acted since
424
+ this.touched();
216
425
  if (key === 'home') {
217
- await pressWdaHome(this.baseUrl, await this.ensureSession());
426
+ await this.withSession(() => pressWdaHome(this.baseUrl)); // session-less route; session kept warm
218
427
  return;
219
428
  }
220
429
  if (key === 'back') {
221
- await pressWdaBack(this.baseUrl, await this.ensureSession());
430
+ // iOS has no system back key (WDA has no /back endpoint): tap the navigation bar's back
431
+ // button when the screen has one, else perform the interactive-pop edge swipe.
432
+ await this.withSession((sid) => this.backIn(sid, cached && cached.sid === sid && cached.epoch === sourceEpochOf(this.udid) && Date.now() - cached.at < WDA_BACK_SOURCE_FRESH_MS
433
+ ? cached.xml
434
+ : undefined));
222
435
  return;
223
436
  }
224
437
  await this.inputText('\n');
225
438
  }
439
+ /** Last `back` strategy used ('nav_button' | 'edge_swipe'), surfaced by qa_act. */
440
+ lastBackVia;
441
+ async backIn(sid, cachedXml) {
442
+ // The latest post-settle source (nothing acted since) is the current screen: try its back
443
+ // button first and only fetch a fresh /source when it has none.
444
+ const cachedBtn = cachedXml ? iosBackButtonPoint(cachedXml) : null;
445
+ if (cachedBtn) {
446
+ await this.timed('tap', () => tapWdaPoint(this.baseUrl, sid, cachedBtn.x, cachedBtn.y));
447
+ this.lastBackVia = 'nav_button';
448
+ return;
449
+ }
450
+ const xml = normalizeWdaSource(await this.timed('source', () => wdaSource(this.baseUrl, sid)));
451
+ const btn = iosBackButtonPoint(xml);
452
+ if (btn) {
453
+ await this.timed('tap', () => tapWdaPoint(this.baseUrl, sid, btn.x, btn.y));
454
+ this.lastBackVia = 'nav_button';
455
+ return;
456
+ }
457
+ let size = null;
458
+ try {
459
+ size = await wdaWindowSize(this.baseUrl, sid);
460
+ }
461
+ catch (e) {
462
+ if (isInvalidWdaSession(e))
463
+ throw e;
464
+ const screen = parseSnapshot(xml).screen;
465
+ size = screen[0] > 0 && screen[1] > 0 ? { width: screen[0], height: screen[1] } : null;
466
+ }
467
+ if (!size)
468
+ throw new Error(IOS_BACK_UNSUPPORTED);
469
+ const y = Math.round(size.height / 2);
470
+ await dragWdaPoint(this.baseUrl, sid, 2, y, Math.round(size.width * 0.6), y, 0.1);
471
+ this.lastBackVia = 'edge_swipe';
472
+ }
226
473
  async acceptAlert() {
227
- await acceptWdaAlert(this.baseUrl, await this.ensureSession());
474
+ this.touched();
475
+ await this.withSession((sid) => acceptWdaAlert(this.baseUrl, sid));
228
476
  }
229
477
  async dismissAlert() {
230
- await dismissWdaAlert(this.baseUrl, await this.ensureSession());
478
+ this.touched();
479
+ await this.withSession((sid) => dismissWdaAlert(this.baseUrl, sid));
231
480
  }
232
481
  async swipe(x1, y1, x2, y2, ms = 300) {
233
- await dragWdaPoint(this.baseUrl, await this.ensureSession(), x1, y1, x2, y2, ms / 1000);
482
+ this.touched();
483
+ await this.withSession((sid) => dragWdaPoint(this.baseUrl, sid, x1, y1, x2, y2, ms / 1000));
234
484
  }
235
485
  adbReverseMetro() {
236
486
  return this.no('dev-server port reverse');
237
487
  }
488
+ // Size in points via GET /window/size instead of dumping the entire page source.
489
+ // Cached per WDA session (a new session naturally misses it) WITHOUT a per-call /orientation
490
+ // round trip: the cache is dropped when a page source shows the axes swapped (rotation), when
491
+ // Swipium changes the orientation (invalidateScreenSizeCache epoch), and after
492
+ // WDA_SCREEN_SIZE_TTL_MS. On a miss, /orientation is still read to key the new entry.
493
+ cachedScreenSize;
238
494
  async screenSize() {
239
- const xml = await this.dumpXml();
240
- const m = xml.match(/bounds="\[0,0\]\[(\d+),(\d+)\]"/);
241
- return m ? { width: Number(m[1]), height: Number(m[2]) } : null;
495
+ const hit = this.cachedScreenSize;
496
+ if (hit &&
497
+ hit.sessionId === this.sessionId &&
498
+ hit.epoch === currentScreenSizeEpoch(this.udid) &&
499
+ Date.now() - hit.at < WDA_SCREEN_SIZE_TTL_MS)
500
+ return { ...hit.size };
501
+ return this.withSession(async (sid) => {
502
+ const epoch = currentScreenSizeEpoch(this.udid);
503
+ const orientation = await wdaOrientation(this.baseUrl, sid).catch(() => 'unknown');
504
+ const cached = this.cachedScreenSize;
505
+ if (cached?.sessionId === sid && cached.orientation === orientation && cached.epoch === epoch) {
506
+ cached.at = Date.now();
507
+ return { ...cached.size };
508
+ }
509
+ let size;
510
+ try {
511
+ size = await wdaWindowSize(this.baseUrl, sid);
512
+ }
513
+ catch (e) {
514
+ if (isInvalidWdaSession(e))
515
+ throw e;
516
+ // older WDA builds lack /window/size, so fall back to the page-source root bounds
517
+ const xml = normalizeWdaSource(await this.timed('source', () => wdaSource(this.baseUrl, sid)));
518
+ const m = xml.match(/bounds="\[0,0\]\[(\d+),(\d+)\]"/);
519
+ size = m ? { width: Number(m[1]), height: Number(m[2]) } : null;
520
+ }
521
+ if (size)
522
+ this.cachedScreenSize = { sessionId: sid, orientation, epoch, at: Date.now(), size };
523
+ return size;
524
+ });
242
525
  }
243
526
  screenDensity() {
244
527
  return Promise.resolve(null);
@@ -246,7 +529,8 @@ export class WdaDriver {
246
529
  async openUrl(url) {
247
530
  if (!this.udid)
248
531
  return this.no('open url without a simulator UDID');
249
- await this.simulator.openUrl(this.udid, url);
532
+ const udid = this.udid;
533
+ await this.simctlChange(() => this.simulator.openUrl(udid, url));
250
534
  }
251
535
  disableAnimations() {
252
536
  return Promise.resolve();
@@ -258,29 +542,55 @@ export class WdaDriver {
258
542
  await this.typeBySelector('accessibility id', value, text);
259
543
  }
260
544
  async tapBySelector(using, value) {
261
- const sid = await this.ensureSession();
262
- const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
263
- await this.timed('tap', () => tapWdaElement(this.baseUrl, sid, el.elementId));
545
+ this.touched();
546
+ await this.withSession(async (sid) => {
547
+ const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
548
+ await this.timed('tap', () => tapWdaElement(this.baseUrl, sid, el.elementId));
549
+ });
264
550
  }
265
551
  async typeBySelector(using, value, text) {
266
- const sid = await this.ensureSession();
267
- const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
268
- await this.timed('type', () => typeWdaElement(this.baseUrl, sid, el.elementId, text));
552
+ this.touched();
553
+ await this.withSession(async (sid) => {
554
+ const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
555
+ await this.timed('type', () => typeWdaElement(this.baseUrl, sid, el.elementId, text));
556
+ });
269
557
  }
270
558
  async clearBySelector(using, value) {
271
- const sid = await this.ensureSession();
272
- const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
273
- await this.timed('clear', () => clearWdaElement(this.baseUrl, sid, el.elementId));
559
+ this.touched();
560
+ await this.withSession(async (sid) => {
561
+ const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
562
+ await this.timed('clear', () => clearWdaElement(this.baseUrl, sid, el.elementId));
563
+ });
274
564
  }
275
- async existsBySelector(using, value) {
276
- const sid = await this.ensureSession();
565
+ // Real secure-field signal for native-selector typing: resolve the element and
566
+ // read its `type` attribute (XCUIElementTypeSecureTextField), accepting a boolean `secure`
567
+ // attribute where a WDA build exposes one instead.
568
+ async isSecureBySelector(using, value) {
569
+ return this.withSession((sid) => this.isSecureIn(sid, using, value));
570
+ }
571
+ async isSecureIn(sid, using, value) {
572
+ const el = await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
277
573
  try {
278
- await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
279
- return true;
574
+ if (/SecureTextField/i.test(await wdaElementAttribute(this.baseUrl, sid, el.elementId, 'type')))
575
+ return true;
280
576
  }
281
577
  catch {
282
- return false;
578
+ // some builds don't serve /attribute/type, so fall through to the `secure` attribute
283
579
  }
580
+ return /^(true|1)$/i.test(await wdaElementAttribute(this.baseUrl, sid, el.elementId, 'secure').catch(() => ''));
581
+ }
582
+ async existsBySelector(using, value) {
583
+ return this.withSession(async (sid) => {
584
+ try {
585
+ await this.timed('find_element', () => findWdaElement(this.baseUrl, sid, using, value));
586
+ return true;
587
+ }
588
+ catch (e) {
589
+ if (isInvalidWdaSession(e))
590
+ throw e; // let withSession recover the session
591
+ return false;
592
+ }
593
+ });
284
594
  }
285
595
  }
286
596
  //# sourceMappingURL=WdaDriver.js.map