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
package/dist/tools/wda.js CHANGED
@@ -1,26 +1,70 @@
1
1
  import { z } from 'zod';
2
2
  import { spawn } from 'node:child_process';
3
- import { existsSync, openSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { closeSync, existsSync, openSync, readFileSync, readdirSync, statSync } from 'node:fs';
4
4
  import { isAbsolute, join } from 'node:path';
5
- import { qaError, qaOk } from '../lib/result.js';
5
+ import { qaError, qaOk, unknownSessionError } from '../lib/result.js';
6
6
  import { consumeConsent, requireConsent } from '../consent/consent.js';
7
7
  import { sensitiveRefusal } from '../lib/sensitive.js';
8
8
  import { run } from '../lib/spawn.js';
9
- import { checkWda, classifyWdaBuildFailure, classifyWdaConnectionFailure, createWdaSession, discoverWdaProjects, managedWdaBuildArgs, managedWdaStartArgs, waitForWdaReady, wdaSessionUdidMismatch, xcodeAvailable } from '../lib/wda.js';
9
+ import { checkWda, classifyWdaBuildFailure, classifyWdaConnectionFailure, createWdaSession, discoverAppiumWdaProjects, discoverWdaProjects, managedWdaBuildArgs, managedWdaStartArgs, waitForWdaReady, wdaSessionUdidMismatch, xcodeAvailable, isLoopbackWdaUrl, remoteWdaAllowedByUser, REMOTE_WDA_ENV, } from '../lib/wda.js';
10
10
  import { loadWdaConfig, wdaSigningStatus, wdaUrlAllowedByConfig } from '../lib/wdaConfig.js';
11
11
  import { recordWdaTiming, wdaRecommendations, wdaTimingSummary } from '../lib/wdaTune.js';
12
12
  import { WdaDriver } from '../drivers/WdaDriver.js';
13
13
  import * as sim from '../lib/simctl.js';
14
+ import { findManagedWdaProcesses, killManagedWda, reclaimPid, registerManagedProcess, registeredWdaForSession, unregisterManagedProcess, } from '../session/processRegistry.js';
14
15
  const managedProcesses = new Map();
15
- function isLoopback(raw) {
16
+ function pidIsAlive(pid) {
17
+ if (!(pid > 0))
18
+ return false;
16
19
  try {
17
- const u = new URL(raw);
18
- return u.protocol === 'http:' && ['127.0.0.1', 'localhost', '::1'].includes(u.hostname);
20
+ process.kill(pid, 0);
21
+ return true;
19
22
  }
20
23
  catch {
21
24
  return false;
22
25
  }
23
26
  }
27
+ /** A managed WDA this server still runs for the session (started here, or adopted at startup).
28
+ * A dead in-memory entry is dropped. */
29
+ function liveManagedWda(sessionId) {
30
+ const own = managedProcesses.get(sessionId);
31
+ if (own) {
32
+ if (pidIsAlive(own.pid))
33
+ return { pid: own.pid, adopted: false };
34
+ managedProcesses.delete(sessionId);
35
+ unregisterManagedProcess(own.pid);
36
+ }
37
+ const adopted = registeredWdaForSession(sessionId);
38
+ return adopted && pidIsAlive(adopted.pid) ? { pid: adopted.pid, adopted: true } : undefined;
39
+ }
40
+ /** The managed-WDA signature of this session's latest successful `qa_wda start` (from the
41
+ * mutation ledger, which survives a server restart). Lets `qa_wda stop` find the xcodebuild
42
+ * even when its process-registry entry was lost. Exported for tests. */
43
+ export function lastManagedWdaStart(session) {
44
+ const m = [...(session.mutations ?? [])]
45
+ .reverse()
46
+ .find((r) => r.tool === 'qa_wda' && r.action === 'wda_start' && r.status === 'executed' && typeof r.target?.projectPath === 'string');
47
+ if (!m)
48
+ return undefined;
49
+ const t = m.target;
50
+ if (typeof t.udid !== 'string' || !t.udid)
51
+ return undefined;
52
+ let port;
53
+ try {
54
+ const u = new URL(String(t.webDriverAgentUrl ?? ''));
55
+ port = Number(u.port) || undefined;
56
+ }
57
+ catch {
58
+ port = undefined;
59
+ }
60
+ return {
61
+ projectPath: t.projectPath,
62
+ udid: t.udid,
63
+ ...(typeof t.derivedDataPath === 'string' && t.derivedDataPath ? { derivedDataPath: t.derivedDataPath } : {}),
64
+ ...(port ? { port } : {}),
65
+ };
66
+ }
67
+ const isLoopback = isLoopbackWdaUrl;
24
68
  function latestWdaArtifacts(session) {
25
69
  const logs = [...session.artifacts].reverse().filter((a) => a.kind === 'wda');
26
70
  const latestBuildLog = logs.find((a) => /build/i.test(a.label ?? a.path)) ?? null;
@@ -76,38 +120,58 @@ function managedWdaBuildProductStatus(derivedDataPath) {
76
120
  export function registerWda(server, sessions) {
77
121
  server.registerTool('qa_wda', {
78
122
  title: 'WebDriverAgent diagnostics and attach',
79
- description: 'Diagnose, attach, or manage an iOS WebDriverAgent backend. External WDA: pass webDriverAgentUrl, then action:"attach". Managed WDA: pass wdaProjectPath + udid for build/start; output is captured as artifacts.',
123
+ description: 'Diagnose, attach, or manage an iOS WebDriverAgent backend for structured iOS tap/type/snapshot. External WDA: ' +
124
+ 'action:"attach" with webDriverAgentUrl (default http://127.0.0.1:8100). Managed WDA: build/start (consent-gated) from ' +
125
+ 'wdaProjectPath or an installed Appium WDA; stop ends it. status/doctor/diagnose/logs/tune inspect a setup.',
80
126
  inputSchema: {
81
127
  sessionId: z.string(),
82
128
  action: z.enum(['status', 'doctor', 'build', 'start', 'stop', 'attach', 'diagnose', 'logs', 'tune']),
83
- webDriverAgentUrl: z.string().optional().describe('External WDA base URL. Defaults to http://127.0.0.1:8100. Non-loopback URLs are refused by default.'),
84
- udid: z.string().optional().describe('Simulator/device UDID expected behind this WDA endpoint.'),
85
- bundleId: z.string().optional().describe('App bundle id for session creation. Defaults to the session appId.'),
86
- wdaProjectPath: z.string().optional().describe('Path to WebDriverAgent.xcodeproj for managed build/start. Relative paths resolve from project root.'),
87
- derivedDataPath: z.string().optional().describe('Optional xcodebuild -derivedDataPath for WDA caching/reuse. Relative paths resolve from project root.'),
129
+ webDriverAgentUrl: z.string().optional().describe('Default http://127.0.0.1:8100; non-loopback needs allowNonLoopback + consent.'),
130
+ device: z.string().optional().describe('Simulator UDID behind this WDA (default: the session device).'),
131
+ udid: z.string().optional().describe('Deprecated alias of device.'),
132
+ bundleId: z.string().optional().describe('Default: the session appId.'),
133
+ wdaProjectPath: z.string().optional().describe('WebDriverAgent.xcodeproj (default: Appium WDA if found).'),
134
+ derivedDataPath: z.string().optional().describe('xcodebuild -derivedDataPath for reuse.'),
88
135
  scheme: z.string().optional().describe('WDA xcodebuild scheme. Defaults to WebDriverAgentRunner.'),
89
136
  allowNonLoopback: z.boolean().optional().describe('Required to use a non-loopback external WDA URL.'),
90
137
  consentId: z.string().optional(),
91
138
  approve: z.boolean().optional(),
92
139
  },
93
- }, async ({ sessionId, action, webDriverAgentUrl, udid, bundleId, wdaProjectPath, derivedDataPath, scheme, allowNonLoopback, consentId, approve }) => {
140
+ }, async ({ sessionId, action, webDriverAgentUrl, device, udid: udidAlias, bundleId, wdaProjectPath, derivedDataPath, scheme, allowNonLoopback, consentId, approve, }) => {
94
141
  const session = sessions.get(sessionId);
95
142
  if (!session)
96
- return qaError({ what: `Unknown sessionId ${sessionId}`, changedState: false, retrySafe: true, nextSteps: ['Call qa_start_session first.'] });
143
+ return unknownSessionError(sessionId);
144
+ // `device` is canonical (as on every other tool); `udid` is the deprecated alias.
145
+ if (device && udidAlias && device !== udidAlias)
146
+ return qaError({
147
+ what: `Conflicting device "${device}" and udid "${udidAlias}"; udid is a deprecated alias of device`,
148
+ changedState: false,
149
+ retrySafe: true,
150
+ failureCode: 'INVALID_ARGUMENT',
151
+ nextSteps: ['Pass only device.'],
152
+ });
153
+ const udid = device ?? udidAlias;
97
154
  const configured = loadWdaConfig(session.root);
98
155
  const url = webDriverAgentUrl ?? configured.url;
99
156
  const loopback = isLoopback(url);
100
- const configAllowed = !loopback && wdaUrlAllowedByConfig(configured, url);
101
- if (!loopback && !allowNonLoopback && !configAllowed) {
157
+ // Only the USER can pre-approve a remote WDA (env var in the MCP client config). The repo's
158
+ // .swipium/config.json (ios.wda.url / allowNonLoopbackUrls) arrives with the checkout, so it
159
+ // can never skip the per-call consent.
160
+ const userAllowed = !loopback && remoteWdaAllowedByUser(url);
161
+ const repoListed = !loopback && wdaUrlAllowedByConfig(configured, url);
162
+ if (!loopback && !allowNonLoopback && !userAllowed) {
102
163
  return qaError({
103
- what: 'Refused non-loopback WDA URL',
164
+ what: `Refused non-loopback WDA URL ${url}${webDriverAgentUrl ? '' : ' (from the repository config .swipium/config.json)'}`,
104
165
  changedState: false,
105
166
  retrySafe: false,
106
167
  failureCode: 'DESTRUCTIVE_REFUSED',
107
- nextSteps: ['Use a localhost WDA URL, pass allowNonLoopback with explicit consent, or add this exact URL to ios.wda.allowNonLoopbackUrls for a trusted isolated automation network.'],
168
+ nextSteps: [
169
+ `Use a localhost WDA URL, pass allowNonLoopback:true and approve the consent prompt, or (user-level, trusted isolated network only) set ${REMOTE_WDA_ENV}=<exact url> in the MCP server environment.`,
170
+ ...(repoListed ? ['ios.wda.allowNonLoopbackUrls in the repository config no longer pre-approves a remote WDA.'] : []),
171
+ ],
108
172
  });
109
173
  }
110
- if (!loopback && !configAllowed) {
174
+ if (!loopback && !userAllowed) {
111
175
  const gate = consumeConsent(consentId, approve, { action: 'wda_non_loopback', affects: { url } });
112
176
  if (!gate.approved) {
113
177
  return requireConsent({
@@ -115,13 +179,21 @@ export function registerWda(server, sessions) {
115
179
  risk: 'medium',
116
180
  exactCommand: `connect to WebDriverAgent at ${url}`,
117
181
  affects: { url },
118
- explain: `Use non-loopback WebDriverAgent URL ${url}? WDA is an automation server; only approve this on a trusted, isolated network.`,
182
+ explain: `Use non-loopback WebDriverAgent URL ${url}${webDriverAgentUrl ? '' : ' (configured by the repository (.swipium/config.json), unreviewed)'}? WDA is an automation server that receives app screens and typed text; only approve this on a trusted, isolated network.`,
119
183
  });
120
184
  }
121
185
  }
122
- const resolvePath = (p) => p ? (isAbsolute(p) ? p : join(session.root, p)) : undefined;
123
- const projectPath = resolvePath(wdaProjectPath);
186
+ const resolvePath = (p) => (p ? (isAbsolute(p) ? p : join(session.root, p)) : undefined);
187
+ const explicitProjectPath = resolvePath(wdaProjectPath);
188
+ // Managed build/start without wdaProjectPath: use a user-installed Appium WebDriverAgent
189
+ // (~/.appium/…/appium-webdriveragent, global npm), reported as wdaProjectSource.
190
+ const discoveredAppiumProject = !explicitProjectPath && (action === 'build' || action === 'start') ? discoverAppiumWdaProjects()[0] : undefined;
191
+ const projectPath = explicitProjectPath ?? discoveredAppiumProject;
192
+ const wdaProjectSource = explicitProjectPath ? 'argument' : discoveredAppiumProject ? 'appium-discovered' : null;
124
193
  const ddPath = resolvePath(derivedDataPath) ?? configured.derivedDataPath;
194
+ // What this call actually uses (args over config), echoed as `wdaConfig` so a passed
195
+ // derivedDataPath / webDriverAgentUrl is not misreported as the configured default.
196
+ const effectiveConfig = { ...configured, url, derivedDataPath: ddPath };
125
197
  const targetUdid = udid ?? session.device;
126
198
  if (udid && session.device && udid !== session.device) {
127
199
  return qaError({
@@ -148,20 +220,60 @@ export function registerWda(server, sessions) {
148
220
  if (action === 'tune') {
149
221
  const recommendations = wdaRecommendations(configured, session);
150
222
  const timings = wdaTimingSummary(session);
151
- return qaOk({ webDriverAgentUrl: url, wdaConfig: configured, timings, recommendations }, recommendations.length
223
+ return qaOk({ webDriverAgentUrl: url, wdaConfig: effectiveConfig, timings, recommendations }, recommendations.length
152
224
  ? `WDA tuning recommendations:\n${recommendations.map((r) => ` - ${r.setting}=${JSON.stringify(r.value)}: ${r.reason}`).join('\n')}`
153
225
  : 'WDA tuning: no recommendations from current session evidence.');
154
226
  }
155
227
  if (action === 'stop') {
156
228
  const proc = managedProcesses.get(session.id);
157
- if (!proc)
158
- return qaOk({ stopped: false }, 'no managed WDA process recorded for this session');
229
+ if (!proc) {
230
+ // A WDA started by a previous server run and adopted at startup (processRegistry).
231
+ const adopted = registeredWdaForSession(session.id);
232
+ const outcome = adopted ? reclaimPid(adopted.pid, 'wda', undefined, adopted) : undefined; // fingerprint-checked
233
+ if (adopted)
234
+ unregisterManagedProcess(adopted.pid);
235
+ if (adopted && outcome === 'killed') {
236
+ sessions.addEnvChange(session, `wda stop pid ${adopted.pid} (adopted)`);
237
+ sessions.recordMutation(session, {
238
+ tool: 'qa_wda',
239
+ action: 'wda_stop',
240
+ risk: 'low',
241
+ target: { pid: adopted.pid, adopted: true, webDriverAgentUrl: adopted.endpoint ?? null },
242
+ consent: { required: false, approved: true },
243
+ status: 'restored',
244
+ detail: `adopted WDA ${outcome}`,
245
+ });
246
+ return qaOk({ stopped: true, pid: adopted.pid, adopted: true, outcome }, `stopped adopted managed WDA pid ${adopted.pid}`);
247
+ }
248
+ // Registry entry lost (or unverifiable): find the managed xcodebuild this session started
249
+ // by its exact signature (project + destination + derived data), never by pid alone.
250
+ const sig = lastManagedWdaStart(session);
251
+ const pids = sig ? findManagedWdaProcesses(sig) : [];
252
+ const killed = pids.filter((pid) => killManagedWda(pid));
253
+ if (!killed.length) {
254
+ if (adopted)
255
+ return qaOk({ stopped: false, pid: adopted.pid, adopted: true, outcome }, `adopted WDA pid ${adopted.pid} was already ${outcome}`);
256
+ return qaOk({ stopped: false }, 'no managed WDA process recorded for this session');
257
+ }
258
+ sessions.addEnvChange(session, `wda stop pid ${killed.join(',')} (recovered by signature)`);
259
+ sessions.recordMutation(session, {
260
+ tool: 'qa_wda',
261
+ action: 'wda_stop',
262
+ risk: 'low',
263
+ target: { pids: killed, recovered: true, projectPath: sig.projectPath, udid: sig.udid },
264
+ consent: { required: false, approved: true },
265
+ status: 'restored',
266
+ detail: 'managed WDA located by command signature (registry entry missing)',
267
+ });
268
+ return qaOk({ stopped: true, pid: killed[0], pids: killed, recovered: true }, `stopped managed WDA pid ${killed.join(', ')} (located by its xcodebuild signature; registry entry was missing)`);
269
+ }
159
270
  try {
160
271
  process.kill(proc.pid, 'SIGTERM');
161
272
  }
162
273
  catch {
163
274
  /* already gone */
164
275
  }
276
+ unregisterManagedProcess(proc.pid);
165
277
  managedProcesses.delete(session.id);
166
278
  sessions.addEnvChange(session, `wda stop pid ${proc.pid}`);
167
279
  sessions.recordMutation(session, {
@@ -176,7 +288,13 @@ export function registerWda(server, sessions) {
176
288
  }
177
289
  if (action === 'build' || action === 'start') {
178
290
  if (!targetUdid) {
179
- return qaError({ what: `qa_wda ${action} requires a simulator/device UDID`, changedState: false, retrySafe: true, failureCode: 'NO_DEVICE', nextSteps: ['Boot/select a simulator with qa_ios boot, or pass udid explicitly.'] });
291
+ return qaError({
292
+ what: `qa_wda ${action} requires a simulator/device UDID`,
293
+ changedState: false,
294
+ retrySafe: true,
295
+ failureCode: 'NO_DEVICE',
296
+ nextSteps: ['Boot/select a simulator with qa_ios boot, or pass device explicitly.'],
297
+ });
180
298
  }
181
299
  if (action === 'start' && configured.reuse) {
182
300
  const reuseStartedAt = Date.now();
@@ -184,19 +302,66 @@ export function registerWda(server, sessions) {
184
302
  const reuseCheckMs = Date.now() - reuseStartedAt;
185
303
  sessions.addMilestoneDuration(session, 'wda_reuse_check_ms', reuseCheckMs);
186
304
  if (existing.reachable && existing.ready) {
187
- return qaOk({ started: false, reused: true, ready: true, webDriverAgentUrl: url, udid: targetUdid, wda: existing, wdaConfig: configured, reuseCheckMs }, `reused existing WDA at ${url}; /status is ready\nNext: qa_wda attach.`);
305
+ return qaOk({
306
+ started: false,
307
+ reused: true,
308
+ ready: true,
309
+ webDriverAgentUrl: url,
310
+ udid: targetUdid,
311
+ wda: existing,
312
+ wdaConfig: effectiveConfig,
313
+ reuseCheckMs,
314
+ }, `reused existing WDA at ${url}; /status is ready\nNext: qa_wda attach.`);
188
315
  }
189
316
  }
317
+ // Never start a second managed xcodebuild over a live one: the new runner would fight it
318
+ // for the port and the old one would be orphaned (managedProcesses would be overwritten).
319
+ const running = action === 'start' ? liveManagedWda(session.id) : undefined;
320
+ if (running) {
321
+ return qaError({
322
+ what: `A managed WDA (pid ${running.pid}${running.adopted ? ', adopted from a previous server run' : ''}) is already running for this session`,
323
+ changedState: false,
324
+ retrySafe: true,
325
+ failureCode: 'WDA_START_FAILED',
326
+ nextSteps: ['Use it (qa_wda attach), or stop it first with qa_wda { action:"stop" } and retry start.'],
327
+ }, { managedPid: running.pid, adopted: running.adopted, webDriverAgentUrl: url });
328
+ }
190
329
  const xcode = await xcodeAvailable();
191
330
  if (!xcode.available) {
192
- return qaError({ what: 'Xcode command line tools are unavailable', changedState: false, retrySafe: true, failureCode: 'BACKEND_UNSUPPORTED', nextSteps: [xcode.error ?? 'Install Xcode and select it with xcode-select.'] }, { xcode });
331
+ return qaError({
332
+ what: 'Xcode command line tools are unavailable',
333
+ changedState: false,
334
+ retrySafe: true,
335
+ failureCode: 'BACKEND_UNSUPPORTED',
336
+ nextSteps: [xcode.error ?? 'Install Xcode and select it with xcode-select.'],
337
+ }, { xcode });
193
338
  }
194
339
  if (!projectPath || !existsSync(projectPath)) {
195
- return qaError({ what: `qa_wda ${action} requires wdaProjectPath pointing to WebDriverAgent.xcodeproj`, changedState: false, retrySafe: true, failureCode: 'NO_ARTIFACT', nextSteps: ['Pass wdaProjectPath, for example path/to/WebDriverAgent.xcodeproj.'] }, { xcode, wdaProjectPath: projectPath ?? null });
340
+ return qaError({
341
+ what: `qa_wda ${action} requires wdaProjectPath pointing to WebDriverAgent.xcodeproj`,
342
+ changedState: false,
343
+ retrySafe: true,
344
+ failureCode: 'NO_ARTIFACT',
345
+ nextSteps: [
346
+ "Pass wdaProjectPath, for example path/to/WebDriverAgent.xcodeproj, or install Appium's WebDriverAgent (appium driver install xcuitest) so it is auto-discovered under ~/.appium.",
347
+ ],
348
+ }, { xcode, wdaProjectPath: projectPath ?? null, wdaProjectSource });
196
349
  }
197
350
  const args = action === 'build'
198
- ? managedWdaBuildArgs({ projectPath, udid: targetUdid, derivedDataPath: ddPath, scheme, developmentTeam: configured.developmentTeam })
199
- : managedWdaStartArgs({ projectPath, udid: targetUdid, derivedDataPath: ddPath, scheme, developmentTeam: configured.developmentTeam });
351
+ ? managedWdaBuildArgs({
352
+ projectPath,
353
+ udid: targetUdid,
354
+ derivedDataPath: ddPath,
355
+ scheme,
356
+ developmentTeam: configured.developmentTeam,
357
+ })
358
+ : managedWdaStartArgs({
359
+ projectPath,
360
+ udid: targetUdid,
361
+ derivedDataPath: ddPath,
362
+ scheme,
363
+ developmentTeam: configured.developmentTeam,
364
+ });
200
365
  const gate = consumeConsent(consentId, approve, { action: `wda_${action}`, affects: { udid: targetUdid, projectPath } });
201
366
  if (!gate.approved) {
202
367
  sessions.recordMutation(session, {
@@ -207,7 +372,13 @@ export function registerWda(server, sessions) {
207
372
  consent: { required: true, approved: false },
208
373
  status: 'requested',
209
374
  });
210
- return requireConsent({ action: `wda_${action}`, risk: 'medium', exactCommand: `xcodebuild ${args.join(' ')}`, affects: { udid: targetUdid, projectPath }, explain: `${action === 'build' ? 'Build' : 'Start'} WebDriverAgent with xcodebuild? This can use local signing context and run for a while.` });
375
+ return requireConsent({
376
+ action: `wda_${action}`,
377
+ risk: 'medium',
378
+ exactCommand: `xcodebuild ${args.join(' ')}`,
379
+ affects: { udid: targetUdid, projectPath },
380
+ explain: `${action === 'build' ? 'Build' : 'Start'} WebDriverAgent with xcodebuild? This can use local signing context and run for a while.`,
381
+ });
211
382
  }
212
383
  sessions.recordMutation(session, {
213
384
  tool: 'qa_wda',
@@ -237,13 +408,17 @@ export function registerWda(server, sessions) {
237
408
  detail: `${failureCode}: exit ${r.code}`,
238
409
  });
239
410
  return qaError({
240
- what: failureCode === 'WDA_SIGNING_FAILED' ? `WDA signing/provisioning failed with exit ${r.code}` : `WDA build failed with exit ${r.code}`,
411
+ what: failureCode === 'WDA_SIGNING_FAILED'
412
+ ? `WDA signing/provisioning failed with exit ${r.code}`
413
+ : `WDA build failed with exit ${r.code}`,
241
414
  changedState: true,
242
415
  retrySafe: failureCode !== 'WDA_SIGNING_FAILED',
243
416
  failureCode,
244
417
  artifactUri: logUri,
245
418
  nextSteps: failureCode === 'WDA_SIGNING_FAILED'
246
- ? ['Open the WDA build log artifact, configure a valid development team/certificate/provisioning profile for the device, then retry.']
419
+ ? [
420
+ 'Open the WDA build log artifact, configure a valid development team/certificate/provisioning profile for the device, then retry.',
421
+ ]
247
422
  : ['Open the WDA build log artifact, fix the Xcode build error, then retry.'],
248
423
  }, { xcode, logUri, timedOut: r.timedOut });
249
424
  }
@@ -256,22 +431,49 @@ export function registerWda(server, sessions) {
256
431
  status: 'executed',
257
432
  ledgerUri: logUri,
258
433
  });
259
- return qaOk({ built: true, logUri, xcode, command: ['xcodebuild', ...args], wdaConfig: configured }, `WDA build completed → ${logUri}`);
434
+ const wdaBuildProduct = managedWdaBuildProductStatus(ddPath);
435
+ return qaOk({
436
+ built: true,
437
+ logUri,
438
+ xcode,
439
+ command: ['xcodebuild', ...args],
440
+ wdaConfig: effectiveConfig,
441
+ wdaProjectPath: projectPath,
442
+ wdaProjectSource,
443
+ derivedDataPath: ddPath,
444
+ wdaBuildProduct,
445
+ }, `WDA build completed (${wdaProjectSource === 'appium-discovered' ? 'auto-discovered Appium WDA ' : ''}${projectPath}) > ${logUri}`);
260
446
  }
261
447
  const logUri = sessions.saveArtifact(session, 'wda', `wda-start-${Date.now()}.log`, '', 'text/plain', 'WDA start log');
262
448
  const rec = sessions.findArtifact(logUri);
263
449
  const fd = openSync(rec.rec.path, 'a');
264
450
  sessions.milestone(session, 'wda_start_start');
265
- const child = spawn('xcodebuild', args, { detached: true, stdio: ['ignore', fd, fd] });
451
+ let child;
452
+ try {
453
+ child = spawn('xcodebuild', args, { detached: true, stdio: ['ignore', fd, fd] });
454
+ }
455
+ finally {
456
+ closeSync(fd); // the child holds its own duplicate; ours would leak one fd per start
457
+ }
266
458
  sessions.milestone(session, 'wda_start_end');
459
+ child.on('error', () => undefined); // spawn failure surfaces as WDA_START_FAILED below, not a crash
267
460
  child.unref();
268
461
  managedProcesses.set(session.id, { pid: child.pid ?? -1, logUri });
462
+ // Reapable if this server crashes; `endpoint` lets the next server adopt it when healthy.
463
+ registerManagedProcess(child.pid, 'wda', session.id, { endpoint: url });
269
464
  sessions.addEnvChange(session, `wda start pid ${child.pid ?? 'unknown'} ${projectPath} ${targetUdid}`);
270
465
  sessions.recordMutation(session, {
271
466
  tool: 'qa_wda',
272
467
  action: 'wda_start',
273
468
  risk: 'medium',
274
- target: { udid: targetUdid, projectPath, derivedDataPath: ddPath, scheme: scheme ?? null, pid: child.pid ?? null, webDriverAgentUrl: url },
469
+ target: {
470
+ udid: targetUdid,
471
+ projectPath,
472
+ derivedDataPath: ddPath,
473
+ scheme: scheme ?? null,
474
+ pid: child.pid ?? null,
475
+ webDriverAgentUrl: url,
476
+ },
275
477
  consent: { required: true, consentId, approved: true },
276
478
  status: 'executed',
277
479
  ledgerUri: logUri,
@@ -284,7 +486,14 @@ export function registerWda(server, sessions) {
284
486
  tool: 'qa_wda',
285
487
  action: 'wda_start',
286
488
  risk: 'medium',
287
- target: { udid: targetUdid, projectPath, derivedDataPath: ddPath, scheme: scheme ?? null, pid: child.pid ?? null, webDriverAgentUrl: url },
489
+ target: {
490
+ udid: targetUdid,
491
+ projectPath,
492
+ derivedDataPath: ddPath,
493
+ scheme: scheme ?? null,
494
+ pid: child.pid ?? null,
495
+ webDriverAgentUrl: url,
496
+ },
288
497
  consent: { required: true, consentId, approved: true },
289
498
  status: 'blocked',
290
499
  ledgerUri: logUri,
@@ -296,10 +505,34 @@ export function registerWda(server, sessions) {
296
505
  retrySafe: true,
297
506
  failureCode: 'WDA_START_FAILED',
298
507
  artifactUri: logUri,
299
- nextSteps: ['Open the WDA start log artifact, fix the launch/signing/device issue, then retry qa_wda start. If WDA eventually becomes ready, qa_wda attach can still use this managed process.'],
300
- }, { started: true, ready: false, pid: child.pid ?? null, logUri, webDriverAgentUrl: url, command: ['xcodebuild', ...args], wdaConfig: configured, wda: waited.status, startupWaitMs: waited.durationMs });
508
+ nextSteps: [
509
+ 'Open the WDA start log artifact, fix the launch/signing/device issue, then retry qa_wda start. If WDA eventually becomes ready, qa_wda attach can still use this managed process.',
510
+ ],
511
+ }, {
512
+ started: true,
513
+ ready: false,
514
+ pid: child.pid ?? null,
515
+ logUri,
516
+ webDriverAgentUrl: url,
517
+ command: ['xcodebuild', ...args],
518
+ wdaConfig: effectiveConfig,
519
+ wda: waited.status,
520
+ startupWaitMs: waited.durationMs,
521
+ });
301
522
  }
302
- return qaOk({ started: true, ready: true, pid: child.pid ?? null, logUri, webDriverAgentUrl: url, command: ['xcodebuild', ...args], wdaConfig: configured, wda: waited.status, startupWaitMs: waited.durationMs }, `started managed WDA pid ${child.pid ?? 'unknown'} and /status is ready → ${logUri}\nNext: qa_wda attach.`);
523
+ return qaOk({
524
+ started: true,
525
+ ready: true,
526
+ pid: child.pid ?? null,
527
+ logUri,
528
+ webDriverAgentUrl: url,
529
+ wdaProjectPath: projectPath,
530
+ wdaProjectSource,
531
+ command: ['xcodebuild', ...args],
532
+ wdaConfig: effectiveConfig,
533
+ wda: waited.status,
534
+ startupWaitMs: waited.durationMs,
535
+ }, `started managed WDA pid ${child.pid ?? 'unknown'} and /status is ready > ${logUri}\nNext: qa_wda attach.`);
303
536
  }
304
537
  const status = await checkWda(url);
305
538
  const xcode = await xcodeAvailable();
@@ -307,7 +540,9 @@ export function registerWda(server, sessions) {
307
540
  const appId = bundleId ?? session.appId ?? null;
308
541
  const artifactSummary = latestWdaArtifacts(session);
309
542
  const wdaProjectDiscovery = discoverWdaProjects(session.root, projectPath ? [projectPath] : []);
310
- const wdaBuildProduct = (configured.mode === 'managed' || !!projectPath) ? managedWdaBuildProductStatus(ddPath) : null;
543
+ // Reported whenever managed WDA is in play OR a derived-data cache exists (e.g. after a
544
+ // qa_wda build that used an auto-discovered Appium project and no explicit path).
545
+ const wdaBuildProduct = configured.mode === 'managed' || !!projectPath || existsSync(ddPath) ? managedWdaBuildProductStatus(ddPath) : null;
311
546
  const simctlOk = await sim.simctlAvailable().catch(() => false);
312
547
  const simulators = simctlOk ? await sim.listSimulators().catch(() => []) : [];
313
548
  const bootedSimulators = simulators.filter((s) => s.state === 'Booted');
@@ -322,7 +557,7 @@ export function registerWda(server, sessions) {
322
557
  bundleId: appId,
323
558
  webDriverAgentUrl: url,
324
559
  wda: status,
325
- wdaConfig: configured,
560
+ wdaConfig: effectiveConfig,
326
561
  wdaProjectDiscovery,
327
562
  signing: wdaSigningStatus(configured),
328
563
  wdaBuildProduct,
@@ -351,13 +586,19 @@ export function registerWda(server, sessions) {
351
586
  issues.push(issue('IOS_SIMULATOR_RUNTIME_MISSING', 'xcrun simctl is available, but no iOS simulators were found', 'Install an iOS simulator runtime in Xcode, create an iPhone simulator, then retry qa_wda doctor.', 'blocker', 'SIMULATOR_RUNTIME_MISSING'));
352
587
  }
353
588
  else if (!bootedUdid && simctlOk && bootedSimulators.length === 0) {
354
- issues.push(issue('NO_BOOTED_SIMULATOR', 'no booted iOS simulator was detected', 'Boot/select a simulator with qa_ios boot, or pass udid for an already-running WDA target.'));
589
+ issues.push(issue('NO_BOOTED_SIMULATOR', 'no booted iOS simulator was detected', 'Boot/select a simulator with qa_ios boot, or pass device for an already-running WDA target.'));
355
590
  }
356
591
  if (!bootedUdid)
357
- issues.push(issue('NO_UDID', 'no simulator/device UDID is bound to this session', 'Boot/select a simulator with qa_ios boot, or pass udid explicitly.'));
358
- if (wantsManaged && (!projectPath || !existsSync(projectPath))) {
592
+ issues.push(issue('NO_UDID', 'no simulator/device UDID is bound to this session', 'Boot/select a simulator with qa_ios boot, or pass device explicitly.'));
593
+ // No path given but a user-installed Appium WDA exists: build/start will auto-use it.
594
+ const appiumAuto = wantsManaged && !projectPath ? discoverAppiumWdaProjects()[0] : undefined;
595
+ if (wantsManaged && (!projectPath || !existsSync(projectPath)) && !appiumAuto) {
359
596
  const discovered = wdaProjectDiscovery.candidates[0];
360
- issues.push(issue('WDA_PROJECT_MISSING', discovered ? `managed WDA needs wdaProjectPath; discovered candidate ${discovered}` : 'managed WDA needs a WebDriverAgent.xcodeproj path; no local candidate was discovered', discovered ? `Pass wdaProjectPath: "${discovered}", or configure ios.wda for that checked-out WebDriverAgent project.` : 'Check out WebDriverAgent/Appium WDA, pass wdaProjectPath, or provide an external WDA URL.'));
597
+ issues.push(issue('WDA_PROJECT_MISSING', discovered
598
+ ? `managed WDA needs wdaProjectPath; discovered candidate ${discovered}`
599
+ : 'managed WDA needs a WebDriverAgent.xcodeproj path; no local candidate was discovered', discovered
600
+ ? `Pass wdaProjectPath: "${discovered}", or configure ios.wda for that checked-out WebDriverAgent project.`
601
+ : 'Check out WebDriverAgent/Appium WDA, pass wdaProjectPath, or provide an external WDA URL.'));
361
602
  }
362
603
  if (wantsManaged && projectPath && existsSync(projectPath) && wdaBuildProduct && !wdaBuildProduct.built)
363
604
  issues.push(issue('WDA_NOT_BUILT', `no WebDriverAgentRunner .app was found under derivedDataPath ${wdaBuildProduct.checkedPath}`, 'Run qa_wda build first, or point ios.wda.derivedDataPath at a cache containing a built WebDriverAgentRunner product.'));
@@ -395,7 +636,9 @@ export function registerWda(server, sessions) {
395
636
  changedState: false,
396
637
  retrySafe: true,
397
638
  failureCode: 'MULTIPLE_DEVICES',
398
- nextSteps: ['Pass udid explicitly, or bind the session to a device first. This prevents attaching to a stale WDA for the wrong device.'],
639
+ nextSteps: [
640
+ 'Pass device explicitly, or bind the session to a device first. This prevents attaching to a stale WDA for the wrong device.',
641
+ ],
399
642
  }, base);
400
643
  }
401
644
  if (!status.reachable) {
@@ -408,7 +651,12 @@ export function registerWda(server, sessions) {
408
651
  }, base);
409
652
  }
410
653
  try {
411
- const sessionOptions = { bundleId: appId ?? undefined, udid: targetUdid, capabilities: configured.capabilities, settings: configured.settings };
654
+ const sessionOptions = {
655
+ bundleId: appId ?? undefined,
656
+ udid: targetUdid,
657
+ capabilities: configured.capabilities,
658
+ settings: configured.settings,
659
+ };
412
660
  const createStarted = Date.now();
413
661
  let created;
414
662
  try {
@@ -424,7 +672,7 @@ export function registerWda(server, sessions) {
424
672
  action: 'wda_attach',
425
673
  risk: 'medium',
426
674
  target: { webDriverAgentUrl: url, udid: targetUdid, bundleId: appId ?? null, reportedDevice: mismatchedUdid },
427
- consent: { required: !loopback && !configAllowed, consentId, approved: loopback || configAllowed || !!approve },
675
+ consent: { required: !loopback && !userAllowed, consentId, approved: loopback || userAllowed || !!approve },
428
676
  status: 'blocked',
429
677
  detail: 'WDA reported a different device',
430
678
  });
@@ -436,7 +684,11 @@ export function registerWda(server, sessions) {
436
684
  nextSteps: ['Stop the stale WDA process or start a new WDA session for the intended UDID.'],
437
685
  }, { ...base, capabilities: created.capabilities ?? null, wdaSessionId: created.sessionId });
438
686
  }
439
- const driver = new WdaDriver(url, { ...sessionOptions, sessionId: created.sessionId, onTiming: (kind, ms) => recordWdaTiming(session, kind, ms, sessions) });
687
+ const driver = new WdaDriver(url, {
688
+ ...sessionOptions,
689
+ sessionId: created.sessionId,
690
+ onTiming: (kind, ms) => recordWdaTiming(session, kind, ms, sessions),
691
+ });
440
692
  session.driver = driver;
441
693
  session.device = targetUdid;
442
694
  if (appId)
@@ -447,8 +699,14 @@ export function registerWda(server, sessions) {
447
699
  tool: 'qa_wda',
448
700
  action: 'wda_attach',
449
701
  risk: 'medium',
450
- target: { webDriverAgentUrl: url, udid: targetUdid, bundleId: appId ?? null, wdaSessionId: created.sessionId, capabilities: created.capabilities ?? null },
451
- consent: { required: !loopback && !configAllowed, consentId, approved: true },
702
+ target: {
703
+ webDriverAgentUrl: url,
704
+ udid: targetUdid,
705
+ bundleId: appId ?? null,
706
+ wdaSessionId: created.sessionId,
707
+ capabilities: created.capabilities ?? null,
708
+ },
709
+ consent: { required: !loopback && !userAllowed, consentId, approved: true },
452
710
  status: 'executed',
453
711
  });
454
712
  return qaOk({ ...base, sessionActive: true, wdaSessionId: created.sessionId, capabilities: created.capabilities ?? null }, `attached WDA structured iOS backend at ${url}\nNext: qa_snapshot / qa_act / qa_flow_run can use WDA-backed structured operations.`);
@@ -460,7 +718,7 @@ export function registerWda(server, sessions) {
460
718
  action: 'wda_attach',
461
719
  risk: 'medium',
462
720
  target: { webDriverAgentUrl: url, udid: targetUdid, bundleId: appId ?? null },
463
- consent: { required: !loopback && !configAllowed, consentId, approved: loopback || configAllowed || !!approve },
721
+ consent: { required: !loopback && !userAllowed, consentId, approved: loopback || userAllowed || !!approve },
464
722
  status: 'blocked',
465
723
  detail: String(e.message ?? e),
466
724
  });