swipium 1.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 (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/dist/appMap/automationLink.js +74 -0
  4. package/dist/appMap/automationLink.js.map +1 -0
  5. package/dist/appMap/build.js +193 -0
  6. package/dist/appMap/build.js.map +1 -0
  7. package/dist/appMap/codeIndex.js +73 -0
  8. package/dist/appMap/codeIndex.js.map +1 -0
  9. package/dist/appMap/featureModel.js +83 -0
  10. package/dist/appMap/featureModel.js.map +1 -0
  11. package/dist/appMap/firstRunApply.js +131 -0
  12. package/dist/appMap/firstRunApply.js.map +1 -0
  13. package/dist/appMap/fsWalk.js +75 -0
  14. package/dist/appMap/fsWalk.js.map +1 -0
  15. package/dist/appMap/issues.js +227 -0
  16. package/dist/appMap/issues.js.map +1 -0
  17. package/dist/appMap/migrations.js +87 -0
  18. package/dist/appMap/migrations.js.map +1 -0
  19. package/dist/appMap/prelaunch.js +66 -0
  20. package/dist/appMap/prelaunch.js.map +1 -0
  21. package/dist/appMap/projectRegistry.js +70 -0
  22. package/dist/appMap/projectRegistry.js.map +1 -0
  23. package/dist/appMap/provenance.js +52 -0
  24. package/dist/appMap/provenance.js.map +1 -0
  25. package/dist/appMap/query.js +118 -0
  26. package/dist/appMap/query.js.map +1 -0
  27. package/dist/appMap/runtimeMerge.js +207 -0
  28. package/dist/appMap/runtimeMerge.js.map +1 -0
  29. package/dist/appMap/schema.js +65 -0
  30. package/dist/appMap/schema.js.map +1 -0
  31. package/dist/appMap/screenMatch.js +75 -0
  32. package/dist/appMap/screenMatch.js.map +1 -0
  33. package/dist/appMap/staticScan.js +614 -0
  34. package/dist/appMap/staticScan.js.map +1 -0
  35. package/dist/appMap/store.js +173 -0
  36. package/dist/appMap/store.js.map +1 -0
  37. package/dist/appMap/tsAstScan.js +184 -0
  38. package/dist/appMap/tsAstScan.js.map +1 -0
  39. package/dist/artifacts/bundletool.js +199 -0
  40. package/dist/artifacts/bundletool.js.map +1 -0
  41. package/dist/artifacts/resolve.js +388 -0
  42. package/dist/artifacts/resolve.js.map +1 -0
  43. package/dist/automationGen/appiumModel.js +129 -0
  44. package/dist/automationGen/appiumModel.js.map +1 -0
  45. package/dist/automationGen/ciEmitter.js +80 -0
  46. package/dist/automationGen/ciEmitter.js.map +1 -0
  47. package/dist/automationGen/jsEmitter.js +440 -0
  48. package/dist/automationGen/jsEmitter.js.map +1 -0
  49. package/dist/automationGen/packagePatch.js +56 -0
  50. package/dist/automationGen/packagePatch.js.map +1 -0
  51. package/dist/automationGen/projectProfile.js +317 -0
  52. package/dist/automationGen/projectProfile.js.map +1 -0
  53. package/dist/automationGen/pythonEmitter.js +386 -0
  54. package/dist/automationGen/pythonEmitter.js.map +1 -0
  55. package/dist/automationGen/readmeEmitter.js +66 -0
  56. package/dist/automationGen/readmeEmitter.js.map +1 -0
  57. package/dist/automationGen/suitePlan.js +94 -0
  58. package/dist/automationGen/suitePlan.js.map +1 -0
  59. package/dist/automationGen/validation.js +150 -0
  60. package/dist/automationGen/validation.js.map +1 -0
  61. package/dist/build/parseBuildLog.js +119 -0
  62. package/dist/build/parseBuildLog.js.map +1 -0
  63. package/dist/build/plan.js +150 -0
  64. package/dist/build/plan.js.map +1 -0
  65. package/dist/ci/preflight.js +85 -0
  66. package/dist/ci/preflight.js.map +1 -0
  67. package/dist/cli/init.js +105 -0
  68. package/dist/cli/init.js.map +1 -0
  69. package/dist/cli/scan.js +85 -0
  70. package/dist/cli/scan.js.map +1 -0
  71. package/dist/cli/suite.js +85 -0
  72. package/dist/cli/suite.js.map +1 -0
  73. package/dist/cli/verify.js +62 -0
  74. package/dist/cli/verify.js.map +1 -0
  75. package/dist/consent/consent.js +43 -0
  76. package/dist/consent/consent.js.map +1 -0
  77. package/dist/context/detect.js +156 -0
  78. package/dist/context/detect.js.map +1 -0
  79. package/dist/context/findApps.js +198 -0
  80. package/dist/context/findApps.js.map +1 -0
  81. package/dist/context/projectRoot.js +41 -0
  82. package/dist/context/projectRoot.js.map +1 -0
  83. package/dist/context/scan.js +167 -0
  84. package/dist/context/scan.js.map +1 -0
  85. package/dist/core/target.js +115 -0
  86. package/dist/core/target.js.map +1 -0
  87. package/dist/core/targetPlan.js +140 -0
  88. package/dist/core/targetPlan.js.map +1 -0
  89. package/dist/drivers/DirectDriver.js +228 -0
  90. package/dist/drivers/DirectDriver.js.map +1 -0
  91. package/dist/drivers/Driver.js +4 -0
  92. package/dist/drivers/Driver.js.map +1 -0
  93. package/dist/drivers/SimctlDriver.js +127 -0
  94. package/dist/drivers/SimctlDriver.js.map +1 -0
  95. package/dist/drivers/WdaDriver.js +250 -0
  96. package/dist/drivers/WdaDriver.js.map +1 -0
  97. package/dist/explore/candidates.js +93 -0
  98. package/dist/explore/candidates.js.map +1 -0
  99. package/dist/explore/graph.js +116 -0
  100. package/dist/explore/graph.js.map +1 -0
  101. package/dist/explore/memory.js +35 -0
  102. package/dist/explore/memory.js.map +1 -0
  103. package/dist/explore/planner.js +105 -0
  104. package/dist/explore/planner.js.map +1 -0
  105. package/dist/explore/policy.js +85 -0
  106. package/dist/explore/policy.js.map +1 -0
  107. package/dist/explore/runner.js +475 -0
  108. package/dist/explore/runner.js.map +1 -0
  109. package/dist/explore/signatures.js +35 -0
  110. package/dist/explore/signatures.js.map +1 -0
  111. package/dist/explore/suite.js +55 -0
  112. package/dist/explore/suite.js.map +1 -0
  113. package/dist/featureTesting/executionBootstrap.js +140 -0
  114. package/dist/featureTesting/executionBootstrap.js.map +1 -0
  115. package/dist/firstRun/authStateMachine.js +83 -0
  116. package/dist/firstRun/authStateMachine.js.map +1 -0
  117. package/dist/firstRun/classifyScreen.js +201 -0
  118. package/dist/firstRun/classifyScreen.js.map +1 -0
  119. package/dist/firstRun/firstRunPlanner.js +207 -0
  120. package/dist/firstRun/firstRunPlanner.js.map +1 -0
  121. package/dist/firstRun/firstRunRunner.js +331 -0
  122. package/dist/firstRun/firstRunRunner.js.map +1 -0
  123. package/dist/firstRun/generatedDataPolicy.js +215 -0
  124. package/dist/firstRun/generatedDataPolicy.js.map +1 -0
  125. package/dist/firstRun/inputPlanner.js +67 -0
  126. package/dist/firstRun/inputPlanner.js.map +1 -0
  127. package/dist/firstRun/onboardingStateMachine.js +46 -0
  128. package/dist/firstRun/onboardingStateMachine.js.map +1 -0
  129. package/dist/firstRun/paywallPolicy.js +54 -0
  130. package/dist/firstRun/paywallPolicy.js.map +1 -0
  131. package/dist/firstRun/types.js +7 -0
  132. package/dist/firstRun/types.js.map +1 -0
  133. package/dist/fixtures/catalog.js +131 -0
  134. package/dist/fixtures/catalog.js.map +1 -0
  135. package/dist/flows/discover.js +18 -0
  136. package/dist/flows/discover.js.map +1 -0
  137. package/dist/flows/generate.js +211 -0
  138. package/dist/flows/generate.js.map +1 -0
  139. package/dist/flows/lint.js +97 -0
  140. package/dist/flows/lint.js.map +1 -0
  141. package/dist/flows/pack.js +68 -0
  142. package/dist/flows/pack.js.map +1 -0
  143. package/dist/flows/run.js +738 -0
  144. package/dist/flows/run.js.map +1 -0
  145. package/dist/flows/schema.js +240 -0
  146. package/dist/flows/schema.js.map +1 -0
  147. package/dist/flows/seedExec.js +108 -0
  148. package/dist/flows/seedExec.js.map +1 -0
  149. package/dist/flows/templates.js +155 -0
  150. package/dist/flows/templates.js.map +1 -0
  151. package/dist/flows/waitGuards.js +12 -0
  152. package/dist/flows/waitGuards.js.map +1 -0
  153. package/dist/index.js +33 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/ios/signing.js +182 -0
  156. package/dist/ios/signing.js.map +1 -0
  157. package/dist/issues/classify.js +186 -0
  158. package/dist/issues/classify.js.map +1 -0
  159. package/dist/issues/fingerprint.js +130 -0
  160. package/dist/issues/fingerprint.js.map +1 -0
  161. package/dist/issues/index.js +240 -0
  162. package/dist/issues/index.js.map +1 -0
  163. package/dist/issues/metrics.js +155 -0
  164. package/dist/issues/metrics.js.map +1 -0
  165. package/dist/issues/recurrence.js +193 -0
  166. package/dist/issues/recurrence.js.map +1 -0
  167. package/dist/issues/report.js +110 -0
  168. package/dist/issues/report.js.map +1 -0
  169. package/dist/issues/reportBridge.js +119 -0
  170. package/dist/issues/reportBridge.js.map +1 -0
  171. package/dist/issues/schema.js +90 -0
  172. package/dist/issues/schema.js.map +1 -0
  173. package/dist/issues/sourceRevision.js +108 -0
  174. package/dist/issues/sourceRevision.js.map +1 -0
  175. package/dist/issues/store.js +155 -0
  176. package/dist/issues/store.js.map +1 -0
  177. package/dist/lib/android.js +278 -0
  178. package/dist/lib/android.js.map +1 -0
  179. package/dist/lib/commandTemplate.js +55 -0
  180. package/dist/lib/commandTemplate.js.map +1 -0
  181. package/dist/lib/coordSpace.js +23 -0
  182. package/dist/lib/coordSpace.js.map +1 -0
  183. package/dist/lib/device.js +87 -0
  184. package/dist/lib/device.js.map +1 -0
  185. package/dist/lib/gitignore.js +22 -0
  186. package/dist/lib/gitignore.js.map +1 -0
  187. package/dist/lib/image.js +97 -0
  188. package/dist/lib/image.js.map +1 -0
  189. package/dist/lib/logger.js +8 -0
  190. package/dist/lib/logger.js.map +1 -0
  191. package/dist/lib/metroState.js +45 -0
  192. package/dist/lib/metroState.js.map +1 -0
  193. package/dist/lib/needsInput.js +201 -0
  194. package/dist/lib/needsInput.js.map +1 -0
  195. package/dist/lib/png.js +103 -0
  196. package/dist/lib/png.js.map +1 -0
  197. package/dist/lib/redact.js +21 -0
  198. package/dist/lib/redact.js.map +1 -0
  199. package/dist/lib/result.js +87 -0
  200. package/dist/lib/result.js.map +1 -0
  201. package/dist/lib/schemaHash.js +65 -0
  202. package/dist/lib/schemaHash.js.map +1 -0
  203. package/dist/lib/sensitive.js +13 -0
  204. package/dist/lib/sensitive.js.map +1 -0
  205. package/dist/lib/simctl.js +149 -0
  206. package/dist/lib/simctl.js.map +1 -0
  207. package/dist/lib/spawn.js +144 -0
  208. package/dist/lib/spawn.js.map +1 -0
  209. package/dist/lib/wda.js +347 -0
  210. package/dist/lib/wda.js.map +1 -0
  211. package/dist/lib/wdaConfig.js +69 -0
  212. package/dist/lib/wdaConfig.js.map +1 -0
  213. package/dist/lib/wdaTune.js +58 -0
  214. package/dist/lib/wdaTune.js.map +1 -0
  215. package/dist/oracle/auth.js +22 -0
  216. package/dist/oracle/auth.js.map +1 -0
  217. package/dist/oracle/failures.js +193 -0
  218. package/dist/oracle/failures.js.map +1 -0
  219. package/dist/oracle/health.js +123 -0
  220. package/dist/oracle/health.js.map +1 -0
  221. package/dist/oracle/record.js +36 -0
  222. package/dist/oracle/record.js.map +1 -0
  223. package/dist/orchestration/envelope.js +52 -0
  224. package/dist/orchestration/envelope.js.map +1 -0
  225. package/dist/orchestration/goal.js +98 -0
  226. package/dist/orchestration/goal.js.map +1 -0
  227. package/dist/plan/plan.js +124 -0
  228. package/dist/plan/plan.js.map +1 -0
  229. package/dist/prompts/index.js +63 -0
  230. package/dist/prompts/index.js.map +1 -0
  231. package/dist/report/artifactManifest.js +44 -0
  232. package/dist/report/artifactManifest.js.map +1 -0
  233. package/dist/report/coverage.js +60 -0
  234. package/dist/report/coverage.js.map +1 -0
  235. package/dist/report/evidence.js +84 -0
  236. package/dist/report/evidence.js.map +1 -0
  237. package/dist/report/export.js +447 -0
  238. package/dist/report/export.js.map +1 -0
  239. package/dist/report/flake.js +61 -0
  240. package/dist/report/flake.js.map +1 -0
  241. package/dist/report/history.js +564 -0
  242. package/dist/report/history.js.map +1 -0
  243. package/dist/report/policy.js +98 -0
  244. package/dist/report/policy.js.map +1 -0
  245. package/dist/report/qaLevel.js +96 -0
  246. package/dist/report/qaLevel.js.map +1 -0
  247. package/dist/report/readiness.js +213 -0
  248. package/dist/report/readiness.js.map +1 -0
  249. package/dist/report/summary.js +42 -0
  250. package/dist/report/summary.js.map +1 -0
  251. package/dist/report/testCatalog.js +93 -0
  252. package/dist/report/testCatalog.js.map +1 -0
  253. package/dist/report/timing.js +37 -0
  254. package/dist/report/timing.js.map +1 -0
  255. package/dist/server.js +180 -0
  256. package/dist/server.js.map +1 -0
  257. package/dist/services/automationGenerate.js +60 -0
  258. package/dist/services/automationGenerate.js.map +1 -0
  259. package/dist/services/build.js +66 -0
  260. package/dist/services/build.js.map +1 -0
  261. package/dist/services/preflight.js +51 -0
  262. package/dist/services/preflight.js.map +1 -0
  263. package/dist/services/prepareAndroid.js +183 -0
  264. package/dist/services/prepareAndroid.js.map +1 -0
  265. package/dist/services/prepareIos.js +162 -0
  266. package/dist/services/prepareIos.js.map +1 -0
  267. package/dist/services/report.js +537 -0
  268. package/dist/services/report.js.map +1 -0
  269. package/dist/services/smoke.js +120 -0
  270. package/dist/services/smoke.js.map +1 -0
  271. package/dist/services/suiteGenerate.js +123 -0
  272. package/dist/services/suiteGenerate.js.map +1 -0
  273. package/dist/services/testSuiteKnowledge.js +150 -0
  274. package/dist/services/testSuiteKnowledge.js.map +1 -0
  275. package/dist/session/attach.js +47 -0
  276. package/dist/session/attach.js.map +1 -0
  277. package/dist/session/progress.js +56 -0
  278. package/dist/session/progress.js.map +1 -0
  279. package/dist/session/store.js +329 -0
  280. package/dist/session/store.js.map +1 -0
  281. package/dist/snapshot/overlays.js +132 -0
  282. package/dist/snapshot/overlays.js.map +1 -0
  283. package/dist/snapshot/parse.js +204 -0
  284. package/dist/snapshot/parse.js.map +1 -0
  285. package/dist/snapshot/present.js +12 -0
  286. package/dist/snapshot/present.js.map +1 -0
  287. package/dist/snapshot/settle.js +36 -0
  288. package/dist/snapshot/settle.js.map +1 -0
  289. package/dist/state/profile.js +318 -0
  290. package/dist/state/profile.js.map +1 -0
  291. package/dist/suite/compile.js +176 -0
  292. package/dist/suite/compile.js.map +1 -0
  293. package/dist/suite/lint.js +45 -0
  294. package/dist/suite/lint.js.map +1 -0
  295. package/dist/suite/pom.js +285 -0
  296. package/dist/suite/pom.js.map +1 -0
  297. package/dist/suite/testcase.js +87 -0
  298. package/dist/suite/testcase.js.map +1 -0
  299. package/dist/testSuite/generator.js +222 -0
  300. package/dist/testSuite/generator.js.map +1 -0
  301. package/dist/testSuite/history.js +44 -0
  302. package/dist/testSuite/history.js.map +1 -0
  303. package/dist/testSuite/issueLinks.js +96 -0
  304. package/dist/testSuite/issueLinks.js.map +1 -0
  305. package/dist/testSuite/merge.js +190 -0
  306. package/dist/testSuite/merge.js.map +1 -0
  307. package/dist/testSuite/schema.js +86 -0
  308. package/dist/testSuite/schema.js.map +1 -0
  309. package/dist/testSuite/store.js +116 -0
  310. package/dist/testSuite/store.js.map +1 -0
  311. package/dist/testSuite/traceability.js +55 -0
  312. package/dist/testSuite/traceability.js.map +1 -0
  313. package/dist/tools/act.js +457 -0
  314. package/dist/tools/act.js.map +1 -0
  315. package/dist/tools/agent.js +225 -0
  316. package/dist/tools/agent.js.map +1 -0
  317. package/dist/tools/appMap.js +317 -0
  318. package/dist/tools/appMap.js.map +1 -0
  319. package/dist/tools/assertVisual.js +74 -0
  320. package/dist/tools/assertVisual.js.map +1 -0
  321. package/dist/tools/automationGenerate.js +394 -0
  322. package/dist/tools/automationGenerate.js.map +1 -0
  323. package/dist/tools/capabilities.js +114 -0
  324. package/dist/tools/capabilities.js.map +1 -0
  325. package/dist/tools/clearOverlay.js +150 -0
  326. package/dist/tools/clearOverlay.js.map +1 -0
  327. package/dist/tools/detectContext.js +41 -0
  328. package/dist/tools/detectContext.js.map +1 -0
  329. package/dist/tools/doctor.js +116 -0
  330. package/dist/tools/doctor.js.map +1 -0
  331. package/dist/tools/explore.js +289 -0
  332. package/dist/tools/explore.js.map +1 -0
  333. package/dist/tools/firstRun.js +136 -0
  334. package/dist/tools/firstRun.js.map +1 -0
  335. package/dist/tools/flow.js +331 -0
  336. package/dist/tools/flow.js.map +1 -0
  337. package/dist/tools/flowGenerate.js +55 -0
  338. package/dist/tools/flowGenerate.js.map +1 -0
  339. package/dist/tools/getArtifact.js +39 -0
  340. package/dist/tools/getArtifact.js.map +1 -0
  341. package/dist/tools/health.js +41 -0
  342. package/dist/tools/health.js.map +1 -0
  343. package/dist/tools/ios.js +492 -0
  344. package/dist/tools/ios.js.map +1 -0
  345. package/dist/tools/jobs.js +20 -0
  346. package/dist/tools/jobs.js.map +1 -0
  347. package/dist/tools/network.js +50 -0
  348. package/dist/tools/network.js.map +1 -0
  349. package/dist/tools/note.js +69 -0
  350. package/dist/tools/note.js.map +1 -0
  351. package/dist/tools/plan.js +49 -0
  352. package/dist/tools/plan.js.map +1 -0
  353. package/dist/tools/prepareIosTarget.js +99 -0
  354. package/dist/tools/prepareIosTarget.js.map +1 -0
  355. package/dist/tools/prepareTarget.js +257 -0
  356. package/dist/tools/prepareTarget.js.map +1 -0
  357. package/dist/tools/report.js +42 -0
  358. package/dist/tools/report.js.map +1 -0
  359. package/dist/tools/screenRecord.js +24 -0
  360. package/dist/tools/screenRecord.js.map +1 -0
  361. package/dist/tools/screenshot.js +63 -0
  362. package/dist/tools/screenshot.js.map +1 -0
  363. package/dist/tools/smoke.js +39 -0
  364. package/dist/tools/smoke.js.map +1 -0
  365. package/dist/tools/snapshot.js +129 -0
  366. package/dist/tools/snapshot.js.map +1 -0
  367. package/dist/tools/startSession.js +169 -0
  368. package/dist/tools/startSession.js.map +1 -0
  369. package/dist/tools/state.js +87 -0
  370. package/dist/tools/state.js.map +1 -0
  371. package/dist/tools/suite.js +412 -0
  372. package/dist/tools/suite.js.map +1 -0
  373. package/dist/tools/testThis.js +774 -0
  374. package/dist/tools/testThis.js.map +1 -0
  375. package/dist/tools/wda.js +477 -0
  376. package/dist/tools/wda.js.map +1 -0
  377. package/dist/version.js +62 -0
  378. package/dist/version.js.map +1 -0
  379. package/dist/visual/ocr.js +72 -0
  380. package/dist/visual/ocr.js.map +1 -0
  381. package/dist/visual/provider.js +75 -0
  382. package/dist/visual/provider.js.map +1 -0
  383. package/docs/README.md +19 -0
  384. package/docs/assets/swipium-lockup-ink-on-light.png +0 -0
  385. package/docs/mcp-server.md +135 -0
  386. package/docs/tools.md +130 -0
  387. package/package.json +70 -0
@@ -0,0 +1,129 @@
1
+ // SWIPIUM-REQ-04 — Cross-platform Appium POM model. Pure transform of the canonical Swipium POM
2
+ // (src/suite/pom.ts) into an Appium-ready, language-agnostic screen model that the JS and Python
3
+ // emitters share. This is the "shared intermediate model" the requirement asks for so generated
4
+ // JS/Python cannot drift from Swipium YAML semantics.
5
+ //
6
+ // Locator policy (REQ-04 §Generated * Suite Requirements + Appium locator-strategy guidance):
7
+ // accessibility id > resource-id/id > iOS predicate/class-chain/name > text > coordinate.
8
+ // XPath is NEVER emitted; coordinate is the explicit non-release-grade fallback.
9
+ /** PomPage name (LoginPage) → screen class name (LoginScreen). */
10
+ export function screenClassName(pageName) {
11
+ return /Page$/.test(pageName) ? pageName.replace(/Page$/, 'Screen') : `${pageName}Screen`;
12
+ }
13
+ function durableStrategy(s) {
14
+ return s === 'accessibilityId' || s === 'id';
15
+ }
16
+ function locator(strategy, value) {
17
+ const durable = durableStrategy(strategy);
18
+ // coordinate is never release-grade; raw text is a semi-fragile fallback but still runnable.
19
+ const releaseGrade = strategy !== 'coordinate';
20
+ return { strategy, value, durable, releaseGrade };
21
+ }
22
+ /**
23
+ * Compute the platform-specific Appium locators for one POM element selectorKind.
24
+ * Returns { android, ios, fallback } — any of which may be undefined.
25
+ */
26
+ function locatorsFor(selectorKind, selector, coords, platforms) {
27
+ const value = selector ?? '';
28
+ switch (selectorKind) {
29
+ case 'accessibility_id': {
30
+ // accessibility id is the one strategy that is durable AND cross-platform.
31
+ const loc = locator('accessibilityId', value);
32
+ return { android: platforms.android ? loc : undefined, ios: platforms.ios ? loc : undefined };
33
+ }
34
+ case 'resource_id':
35
+ return { android: platforms.android ? locator('id', value) : undefined };
36
+ case 'name':
37
+ return { ios: platforms.ios ? locator('name', value) : undefined };
38
+ case 'predicate':
39
+ return { ios: platforms.ios ? locator('iosPredicate', value) : undefined };
40
+ case 'class_chain':
41
+ return { ios: platforms.ios ? locator('iosClassChain', value) : undefined };
42
+ case 'text': {
43
+ // text → Android UiAutomator text match (semi), iOS name match (semi). Both fragile.
44
+ const android = platforms.android ? locator('androidUiautomator', value) : undefined;
45
+ const ios = platforms.ios ? locator('name', value) : undefined;
46
+ return { android, ios, fallback: locator('androidUiautomator', value) };
47
+ }
48
+ case 'coords':
49
+ default: {
50
+ const c = coords ?? [0, 0];
51
+ return { fallback: locator('coordinate', `${c[0]},${c[1]}`) };
52
+ }
53
+ }
54
+ }
55
+ /**
56
+ * Build the cross-platform Appium model from a generated POM + the resolved platform support.
57
+ */
58
+ export function buildAppiumModel(pom, opts) {
59
+ const platforms = opts.platforms;
60
+ const secrets = new Set();
61
+ const variables = new Set();
62
+ const screens = pom.pages.map((p) => {
63
+ const className = screenClassName(p.name);
64
+ const elements = p.elements.map((e) => {
65
+ const { android, ios, fallback } = locatorsFor(e.selectorKind, e.selector, e.coords, platforms);
66
+ return {
67
+ name: e.name,
68
+ role: undefined,
69
+ android,
70
+ ios,
71
+ fallback,
72
+ durability: e.durability,
73
+ required: e.required,
74
+ secure: e.secure,
75
+ remediation: e.remediation,
76
+ mapScreenId: p.screen,
77
+ sourceFile: opts.sourceFilePrefix ? `${opts.sourceFilePrefix}/pages/${kebab(p.name)}.page.yaml` : `pages/${kebab(p.name)}.page.yaml`,
78
+ };
79
+ });
80
+ return { className, pageName: p.name, screenSignature: p.screen, elements };
81
+ });
82
+ const steps = pom.steps.map((s) => toStep(s, secrets, variables));
83
+ return {
84
+ testName: pom.testName,
85
+ screens,
86
+ steps,
87
+ variables: [...variables],
88
+ secrets: [...secrets],
89
+ audit: pom.audit,
90
+ platforms,
91
+ };
92
+ }
93
+ function toStep(s, secrets, variables) {
94
+ const screen = screenClassName(s.page);
95
+ const base = { screen, element: s.element, action: s.action };
96
+ if (s.action === 'inputText') {
97
+ const m = (s.text ?? '').match(/^\$\{([^}]+)\}$/);
98
+ if (m) {
99
+ if (s.secret)
100
+ secrets.add(m[1]);
101
+ else
102
+ variables.add(m[1]);
103
+ return { ...base, action: 'inputText', text: s.text, secret: s.secret, varName: m[1] };
104
+ }
105
+ // literal non-secret text — also scan for embedded ${VARS}
106
+ for (const mm of (s.text ?? '').matchAll(/\$\{([^}]+)\}/g))
107
+ variables.add(mm[1]);
108
+ return { ...base, action: 'inputText', text: s.text ?? '', secret: s.secret };
109
+ }
110
+ if (s.action === 'tap' && !s.element && s.coords)
111
+ return { ...base, action: 'tapAt', coords: s.coords };
112
+ if (s.action === 'press')
113
+ return { ...base, action: 'press', key: s.key };
114
+ if (s.action === 'swipe')
115
+ return { ...base, action: 'swipe', direction: s.direction };
116
+ if (s.action === 'openUrl')
117
+ return { ...base, action: 'openUrl', url: s.url };
118
+ if (s.action === 'assertVisible')
119
+ return { ...base, action: 'assertVisible', text: s.text };
120
+ return base;
121
+ }
122
+ function kebab(s) {
123
+ return s
124
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
125
+ .replace(/[^A-Za-z0-9]+/g, '-')
126
+ .replace(/^-+|-+$/g, '')
127
+ .toLowerCase();
128
+ }
129
+ //# sourceMappingURL=appiumModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appiumModel.js","sourceRoot":"","sources":["../../src/automationGen/appiumModel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ // SWIPIUM-REQ-04 — CI example emitter. Pure: produces a LOCAL-Appium CI template (GitHub Actions)
2
+ // for the generated JS or Python suite. Per the non-goals, this is a starting example only (no cloud
3
+ // device-farm config), and the tool writes it as `ci.example.yml` — never an active workflow.
4
+ export function emitCiExample(language, opts = {}) {
5
+ if (language === 'python')
6
+ return pythonCi();
7
+ return jsCi(language);
8
+ }
9
+ function jsCi(language) {
10
+ const installTs = language === 'typescript' ? '' : '';
11
+ return `# Generated by Swipium — EXAMPLE local-Appium CI (rename to .github/workflows/*.yml to enable).
12
+ # Local Appium + Android emulator only. No cloud device farm (out of scope for v1).
13
+ name: appium-e2e (example)
14
+ on:
15
+ workflow_dispatch: {}
16
+ jobs:
17
+ android-e2e:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-node@v4
22
+ with:
23
+ node-version: 20
24
+ - name: Install deps
25
+ run: npm install${installTs}
26
+ - name: Enable KVM
27
+ run: |
28
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
29
+ sudo udevadm control --reload-rules && sudo udevadm trigger --name-match=kvm
30
+ - name: Run Appium e2e on emulator
31
+ uses: reactivecircus/android-emulator-runner@v2
32
+ with:
33
+ api-level: 34
34
+ arch: x86_64
35
+ script: |
36
+ npx appium &
37
+ sleep 8
38
+ ANDROID_APP_PACKAGE=\${{ env.ANDROID_APP_PACKAGE }} npm test
39
+ env:
40
+ # Provide secrets via repo/Org secrets — never commit them.
41
+ SWIPIUM_TEST_PASSWORD: \${{ secrets.SWIPIUM_TEST_PASSWORD }}
42
+ `;
43
+ }
44
+ function pythonCi() {
45
+ return `# Generated by Swipium — EXAMPLE local-Appium CI (rename to .github/workflows/*.yml to enable).
46
+ # Local Appium + Android emulator only. No cloud device farm (out of scope for v1).
47
+ name: appium-e2e (example)
48
+ on:
49
+ workflow_dispatch: {}
50
+ jobs:
51
+ android-e2e:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: actions/setup-python@v5
56
+ with:
57
+ python-version: '3.12'
58
+ - name: Install deps
59
+ run: |
60
+ pip install -r requirements.txt
61
+ npm install -g appium
62
+ appium driver install uiautomator2
63
+ - name: Enable KVM
64
+ run: |
65
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
66
+ sudo udevadm control --reload-rules && sudo udevadm trigger --name-match=kvm
67
+ - name: Run Appium e2e on emulator
68
+ uses: reactivecircus/android-emulator-runner@v2
69
+ with:
70
+ api-level: 34
71
+ arch: x86_64
72
+ script: |
73
+ appium &
74
+ sleep 8
75
+ ANDROID_APP_PACKAGE=\${{ env.ANDROID_APP_PACKAGE }} pytest
76
+ env:
77
+ SWIPIUM_TEST_PASSWORD: \${{ secrets.SWIPIUM_TEST_PASSWORD }}
78
+ `;
79
+ }
80
+ //# sourceMappingURL=ciEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ciEmitter.js","sourceRoot":"","sources":["../../src/automationGen/ciEmitter.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,qGAAqG;AACrG,8FAA8F;AAI9F,MAAM,UAAU,aAAa,CAAC,QAA4B,EAAE,OAA2B,EAAE;IACvF,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,EAAE,CAAC;IAC7C,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,IAAI,CAAC,QAA4B;IACxC,MAAM,SAAS,GAAG,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO;;;;;;;;;;;;;;0BAciB,SAAS;;;;;;;;;;;;;;;;;CAiBlC,CAAC;AACF,CAAC;AAED,SAAS,QAAQ;IACf,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCR,CAAC;AACF,CAAC"}
@@ -0,0 +1,440 @@
1
+ // SWIPIUM-REQ-04 — JS/TS WebdriverIO + Appium emitter. Pure: turns the shared Appium model into a
2
+ // runnable WebdriverIO POM suite (page/screen classes with locator getters + action methods, env-
3
+ // driven capabilities, structured waits). No XPath is ever emitted; coordinate fallbacks are marked
4
+ // non-release-grade. Aligns with the Appium JS quickstart (WebdriverIO) locator-strategy guidance.
5
+ const DEFAULT_TIMEOUT = 15000;
6
+ function pascal(s) {
7
+ const parts = s.replace(/[^A-Za-z0-9]+/g, ' ').trim().split(/\s+/).filter(Boolean);
8
+ return parts.map((p) => p[0].toUpperCase() + p.slice(1)).join('') || 'X';
9
+ }
10
+ /** Env var (SWIPIUM_TEST_PASSWORD) → a stable testData key (password). */
11
+ export function varToKey(v) {
12
+ const stripped = v.replace(/^SWIPIUM_(TEST_)?/i, '');
13
+ const parts = stripped.replace(/[^A-Za-z0-9]+/g, ' ').trim().toLowerCase().split(/\s+/).filter(Boolean);
14
+ if (!parts.length)
15
+ return 'value';
16
+ return parts.map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1))).join('');
17
+ }
18
+ /** wdio selector string for a single Appium locator (no XPath, ever). */
19
+ export function wdioSelector(loc) {
20
+ switch (loc.strategy) {
21
+ case 'accessibilityId':
22
+ return `~${loc.value}`;
23
+ case 'id':
24
+ return `id=${loc.value}`;
25
+ case 'androidUiautomator':
26
+ return `android=new UiSelector().text(${JSON.stringify(loc.value)})`;
27
+ case 'name':
28
+ return `-ios predicate string:name == ${JSON.stringify(loc.value)} OR label == ${JSON.stringify(loc.value)}`;
29
+ case 'iosPredicate':
30
+ return `-ios predicate string:${loc.value}`;
31
+ case 'iosClassChain':
32
+ return `-ios class chain:${loc.value}`;
33
+ case 'coordinate':
34
+ return '';
35
+ default:
36
+ return '';
37
+ }
38
+ }
39
+ function platformLocatorLiteral(el) {
40
+ const parts = [];
41
+ if (el.android)
42
+ parts.push(`android: ${JSON.stringify(wdioSelector(el.android))}`);
43
+ if (el.ios)
44
+ parts.push(`ios: ${JSON.stringify(wdioSelector(el.ios))}`);
45
+ if (el.fallback && el.fallback.strategy === 'coordinate')
46
+ parts.push(`coordinate: ${JSON.stringify(el.fallback.value)}`);
47
+ else if (el.fallback && !el.android && !el.ios)
48
+ parts.push(`android: ${JSON.stringify(wdioSelector(el.fallback))}`);
49
+ return `{ ${parts.join(', ')} }`;
50
+ }
51
+ export function emitJsSuite(input) {
52
+ const ts = input.language === 'typescript';
53
+ const ext = ts ? 'ts' : 'js';
54
+ const t = (annotation) => (ts ? annotation : '');
55
+ const files = [];
56
+ files.push({ path: 'package.json', content: packageJson(input) });
57
+ if (ts)
58
+ files.push({ path: 'tsconfig.json', content: tsconfigJson() });
59
+ files.push({ path: `wdio.conf.${ext}`, content: wdioConf(ts, ext) });
60
+ files.push({ path: `src/config/capabilities.${ext}`, content: capabilities(input, ts) });
61
+ files.push({ path: `src/utils/locators.${ext}`, content: locatorsUtil(ts) });
62
+ files.push({ path: `src/utils/waits.${ext}`, content: waitsUtil(ts) });
63
+ files.push({ path: `src/screens/BaseScreen.${ext}`, content: baseScreen(ts) });
64
+ for (const screen of input.model.screens) {
65
+ files.push({ path: `src/screens/${screen.className}.${ext}`, content: screenClass(screen, input.model.steps, ts, t) });
66
+ }
67
+ files.push({ path: `src/data/testData.${ext}`, content: testData(input.model, ts) });
68
+ files.push({ path: `test/smoke.e2e.${ext}`, content: smokeTest(input.model, ts) });
69
+ return files;
70
+ }
71
+ function packageJson(input) {
72
+ const ts = input.language === 'typescript';
73
+ const name = `${(input.appId ?? 'app').split('.').pop()}-appium-suite`;
74
+ const pkg = {
75
+ name,
76
+ private: true,
77
+ type: 'module',
78
+ description: 'Generated by Swipium — WebdriverIO + Appium POM suite (review before committing).',
79
+ scripts: {
80
+ test: 'wdio run ./wdio.conf.' + (ts ? 'ts' : 'js'),
81
+ },
82
+ devDependencies: {
83
+ '@wdio/cli': '^9.0.0',
84
+ '@wdio/local-runner': '^9.0.0',
85
+ '@wdio/mocha-framework': '^9.0.0',
86
+ '@wdio/appium-service': '^9.0.0',
87
+ '@wdio/spec-reporter': '^9.0.0',
88
+ appium: '^2.11.0',
89
+ ...(ts ? { typescript: '^5.7.0', tsx: '^4.19.0', '@types/node': '^22.0.0' } : {}),
90
+ },
91
+ };
92
+ return JSON.stringify(pkg, null, 2) + '\n';
93
+ }
94
+ function tsconfigJson() {
95
+ return JSON.stringify({
96
+ compilerOptions: {
97
+ target: 'ES2022',
98
+ module: 'NodeNext',
99
+ moduleResolution: 'NodeNext',
100
+ types: ['node', '@wdio/globals/types', '@wdio/mocha-framework'],
101
+ strict: true,
102
+ esModuleInterop: true,
103
+ skipLibCheck: true,
104
+ },
105
+ include: ['src/**/*.ts', 'test/**/*.ts', 'wdio.conf.ts'],
106
+ }, null, 2) + '\n';
107
+ }
108
+ function wdioConf(ts, ext) {
109
+ const header = '// Generated by Swipium. WebdriverIO + Appium config — capabilities come from env (see src/config/capabilities).';
110
+ const importLine = `import { buildCapabilities } from './src/config/capabilities.js';`;
111
+ const typeAnno = ts ? `\nimport type { Options } from '@wdio/types';\n` : '\n';
112
+ const cfgType = ts ? ': WebdriverIO.Config' : '';
113
+ return `${header}${typeAnno}${importLine}
114
+
115
+ export const config${cfgType} = {
116
+ runner: 'local',
117
+ hostname: process.env.APPIUM_HOST || '127.0.0.1',
118
+ port: Number(process.env.APPIUM_PORT || 4723),
119
+ path: '/',
120
+ specs: ['./test/**/*.e2e.${ext}'],
121
+ maxInstances: 1,
122
+ capabilities: [buildCapabilities()],
123
+ logLevel: 'info',
124
+ waitforTimeout: ${DEFAULT_TIMEOUT},
125
+ framework: 'mocha',
126
+ reporters: ['spec'],
127
+ mochaOpts: { ui: 'bdd', timeout: 120000 },
128
+ services: [['appium', { args: { address: process.env.APPIUM_HOST || '127.0.0.1' } }]],
129
+ };
130
+ `;
131
+ }
132
+ function capabilities(input, ts) {
133
+ const android = input.model.platforms.android;
134
+ const ios = input.model.platforms.ios;
135
+ const ret = ts ? ': WebdriverIO.Capabilities' : '';
136
+ const appId = input.appId ?? '';
137
+ const lines = [];
138
+ lines.push('// Generated by Swipium. ALL device/app config is environment-driven — no secrets, no hardcoded paths.');
139
+ lines.push('// Android is the default fast-feedback backend; set SWIPIUM_PLATFORM=ios to target iOS (XCUITest).');
140
+ lines.push('');
141
+ lines.push(`export function buildCapabilities()${ret} {`);
142
+ lines.push(` const platform = (process.env.SWIPIUM_PLATFORM || ${JSON.stringify(android ? 'android' : 'ios')}).toLowerCase();`);
143
+ lines.push(' if (platform === \'ios\') {');
144
+ lines.push(' return {');
145
+ lines.push(' platformName: \'iOS\',');
146
+ lines.push(' \'appium:automationName\': \'XCUITest\',');
147
+ lines.push(' \'appium:deviceName\': process.env.IOS_DEVICE_NAME || \'iPhone 15\',');
148
+ lines.push(' \'appium:platformVersion\': process.env.IOS_PLATFORM_VERSION,');
149
+ lines.push(` 'appium:bundleId': process.env.IOS_BUNDLE_ID${appId ? ` || ${JSON.stringify(appId)}` : ''},`);
150
+ lines.push(' \'appium:app\': process.env.IOS_APP_PATH,');
151
+ lines.push(' \'appium:udid\': process.env.IOS_UDID,');
152
+ lines.push(' \'appium:noReset\': process.env.SWIPIUM_NO_RESET === \'true\',');
153
+ lines.push(' };');
154
+ lines.push(' }');
155
+ lines.push(' // Android UiAutomator2 (default).');
156
+ lines.push(' return {');
157
+ lines.push(' platformName: \'Android\',');
158
+ lines.push(' \'appium:automationName\': \'UiAutomator2\',');
159
+ lines.push(' \'appium:deviceName\': process.env.ANDROID_DEVICE_NAME || \'Android Emulator\',');
160
+ lines.push(` 'appium:appPackage': process.env.ANDROID_APP_PACKAGE${appId ? ` || ${JSON.stringify(appId)}` : ''},`);
161
+ lines.push(' \'appium:appActivity\': process.env.ANDROID_APP_ACTIVITY,');
162
+ lines.push(' \'appium:app\': process.env.ANDROID_APP_PATH,');
163
+ lines.push(' \'appium:udid\': process.env.ANDROID_UDID,');
164
+ lines.push(' \'appium:noReset\': process.env.SWIPIUM_NO_RESET === \'true\',');
165
+ lines.push(' };');
166
+ lines.push('}');
167
+ lines.push('');
168
+ return lines.join('\n');
169
+ }
170
+ function locatorsUtil(ts) {
171
+ const typeBlock = ts
172
+ ? `export interface PlatformLocator {\n android?: string;\n ios?: string;\n /** "x,y" coordinate fallback — non-release-grade. */\n coordinate?: string;\n}\n\n`
173
+ : '';
174
+ const param = ts ? 'loc: PlatformLocator' : 'loc';
175
+ const ret = ts ? ': string' : '';
176
+ return `// Generated by Swipium. Resolves a centralized cross-platform locator to a wdio selector at runtime.
177
+ ${typeBlock}export function resolveSelector(${param})${ret} {
178
+ // @ts-ignore wdio global
179
+ const android = typeof driver !== 'undefined' ? driver.isAndroid : true;
180
+ const selector = android ? loc.android : loc.ios;
181
+ if (!selector) {
182
+ throw new Error('No durable selector for this platform — element relies on a coordinate fallback (non-release-grade). Add an accessibility id / resource-id / testID.');
183
+ }
184
+ return selector;
185
+ }
186
+
187
+ export function coordinateOf(${param})${ts ? ': [number, number] | undefined' : ''} {
188
+ if (!loc.coordinate) return undefined;
189
+ const [x, y] = loc.coordinate.split(',').map(Number);
190
+ return [x, y];
191
+ }
192
+ `;
193
+ }
194
+ function waitsUtil(ts) {
195
+ const elAnno = ts ? ': WebdriverIO.Element' : '';
196
+ const numAnno = ts ? ': number' : '';
197
+ return `// Generated by Swipium. Structured waits — never a bare sleep. Auto-wait before interaction
198
+ // (Playwright-style resilience, applied to mobile Appium).
199
+ export const DEFAULT_TIMEOUT = ${DEFAULT_TIMEOUT};
200
+
201
+ export async function waitVisible(el${elAnno}, timeout${numAnno} = DEFAULT_TIMEOUT) {
202
+ await el.waitForDisplayed({ timeout });
203
+ return el;
204
+ }
205
+
206
+ export async function waitEnabled(el${elAnno}, timeout${numAnno} = DEFAULT_TIMEOUT) {
207
+ await el.waitForEnabled({ timeout });
208
+ return el;
209
+ }
210
+ `;
211
+ }
212
+ function baseScreen(ts) {
213
+ const locImport = ts ? `import type { PlatformLocator } from '../utils/locators.js';\n` : '';
214
+ const p = (name, type) => (ts ? `${name}: ${type}` : name);
215
+ return `// Generated by Swipium. BaseScreen centralizes all element interaction so selectors are
216
+ // resolved per-platform in ONE place and tests never touch raw selectors.
217
+ ${locImport}import { resolveSelector, coordinateOf } from '../utils/locators.js';
218
+ import { waitVisible, waitEnabled, DEFAULT_TIMEOUT } from '../utils/waits.js';
219
+
220
+ export class BaseScreen {
221
+ protected async element(${p('loc', 'PlatformLocator')}) {
222
+ // @ts-ignore wdio global
223
+ return $(resolveSelector(loc));
224
+ }
225
+
226
+ protected async tap(${p('loc', 'PlatformLocator')}) {
227
+ const el = await this.element(loc);
228
+ await waitEnabled(await waitVisible(el));
229
+ await el.click();
230
+ }
231
+
232
+ protected async type(${p('loc', 'PlatformLocator')}, ${p('value', 'string')}) {
233
+ const el = await this.element(loc);
234
+ await waitVisible(el);
235
+ await el.setValue(value);
236
+ }
237
+
238
+ protected async scrollTo(${p('loc', 'PlatformLocator')}) {
239
+ const el = await this.element(loc);
240
+ await el.scrollIntoView();
241
+ await waitVisible(el);
242
+ }
243
+
244
+ protected async isVisible(${p('loc', 'PlatformLocator')})${ts ? ': Promise<boolean>' : ''} {
245
+ const el = await this.element(loc);
246
+ return el.isDisplayed();
247
+ }
248
+
249
+ async assertVisible(${p('loc', 'PlatformLocator')}) {
250
+ const el = await this.element(loc);
251
+ await waitVisible(el);
252
+ }
253
+
254
+ async assertTextVisible(${p('text', 'string')}) {
255
+ // Non-release-grade text/OCR assertion — prefer a structured locator.
256
+ // @ts-ignore wdio global
257
+ const el = await $('android=new UiSelector().textContains(' + JSON.stringify(text) + ')');
258
+ await waitVisible(el);
259
+ }
260
+
261
+ // Coordinate tap — brittle, non-release-grade fallback. Only used when no durable locator exists.
262
+ async tapAt(${p('x', 'number')}, ${p('y', 'number')}) {
263
+ // @ts-ignore wdio global
264
+ await driver.action('pointer')
265
+ .move({ x, y }).down().pause(50).up().perform();
266
+ }
267
+
268
+ async pressKey(${p('key', 'string')}) {
269
+ // @ts-ignore wdio global
270
+ if (driver.isAndroid && key === 'back') { await driver.back(); return; }
271
+ // @ts-ignore wdio global
272
+ await driver.keys([key]);
273
+ }
274
+
275
+ async swipe(${p('direction', 'string')}) {
276
+ // @ts-ignore wdio global
277
+ const { width, height } = await driver.getWindowSize();
278
+ const cx = Math.round(width / 2), cy = Math.round(height / 2);
279
+ const map = {
280
+ up: [cx, Math.round(height * 0.8), cx, Math.round(height * 0.2)],
281
+ down: [cx, Math.round(height * 0.2), cx, Math.round(height * 0.8)],
282
+ left: [Math.round(width * 0.8), cy, Math.round(width * 0.2), cy],
283
+ right: [Math.round(width * 0.2), cy, Math.round(width * 0.8), cy],
284
+ };
285
+ const [x1, y1, x2, y2] = map[${ts ? 'direction as keyof typeof map' : 'direction'}] || map.up;
286
+ // @ts-ignore wdio global
287
+ await driver.action('pointer')
288
+ .move({ x: x1, y: y1 }).down().pause(100).move({ duration: 600, x: x2, y: y2 }).up().perform();
289
+ }
290
+
291
+ async openUrl(${p('url', 'string')}) {
292
+ // @ts-ignore wdio global
293
+ await driver.url(url);
294
+ }
295
+ }
296
+ `;
297
+ }
298
+ /** Per-screen action method name for an (element, action) pair. */
299
+ function methodName(action, element) {
300
+ const E = pascal(element);
301
+ switch (action) {
302
+ case 'tap':
303
+ return `tap${E}`;
304
+ case 'inputText':
305
+ return `enter${E}`;
306
+ case 'scrollTo':
307
+ return `scrollTo${E}`;
308
+ case 'assertVisible':
309
+ return `assert${E}Visible`;
310
+ default:
311
+ return `act${E}`;
312
+ }
313
+ }
314
+ function screenClass(screen, steps, ts, t) {
315
+ const locImport = ts ? `import type { PlatformLocator } from '../utils/locators.js';\n` : '';
316
+ const lines = [];
317
+ lines.push(`// Generated by Swipium. Screen object for ${screen.pageName}${screen.screenSignature ? ` (${screen.screenSignature})` : ''}.`);
318
+ lines.push('// Selectors are centralized here; tests call the action methods below.');
319
+ lines.push(locImport.trimEnd());
320
+ lines.push(`import { BaseScreen } from './BaseScreen.js';`);
321
+ lines.push('');
322
+ lines.push(`export class ${screen.className} extends BaseScreen {`);
323
+ // Locators (centralized).
324
+ for (const el of screen.elements) {
325
+ const durabilityNote = el.durability === 'durable' ? '' : ` // ${el.durability} locator${el.remediation ? ` — ${el.remediation}` : ''}`;
326
+ lines.push(` readonly ${el.name}${t(': PlatformLocator')} = ${platformLocatorLiteral(el)};${durabilityNote}`);
327
+ }
328
+ lines.push('');
329
+ // Action methods for each (element, action) used in steps on this screen.
330
+ const seen = new Set();
331
+ for (const s of steps) {
332
+ if (s.screen !== screen.className || !s.element)
333
+ continue;
334
+ const el = screen.elements.find((e) => e.name === s.element);
335
+ if (!el)
336
+ continue;
337
+ const name = methodName(s.action, s.element);
338
+ if (seen.has(name))
339
+ continue;
340
+ seen.add(name);
341
+ if (s.action === 'inputText') {
342
+ lines.push(` async ${name}(${ts ? 'value: string' : 'value'}) {`);
343
+ lines.push(` await this.type(this.${s.element}, value);`);
344
+ lines.push(' }');
345
+ }
346
+ else if (s.action === 'tap') {
347
+ lines.push(` async ${name}() {`);
348
+ lines.push(` await this.tap(this.${s.element});`);
349
+ lines.push(' }');
350
+ }
351
+ else if (s.action === 'scrollTo') {
352
+ lines.push(` async ${name}() {`);
353
+ lines.push(` await this.scrollTo(this.${s.element});`);
354
+ lines.push(' }');
355
+ }
356
+ else if (s.action === 'assertVisible') {
357
+ lines.push(` async ${name}() {`);
358
+ lines.push(` await this.assertVisible(this.${s.element});`);
359
+ lines.push(' }');
360
+ }
361
+ }
362
+ lines.push('}');
363
+ lines.push('');
364
+ return lines.join('\n');
365
+ }
366
+ function testData(model, ts) {
367
+ const lines = [];
368
+ lines.push('// Generated by Swipium. Test data comes from the ENVIRONMENT — secrets are never inlined.');
369
+ lines.push('export const testData = {');
370
+ for (const v of model.variables) {
371
+ lines.push(` ${varToKey(v)}: process.env.${v} || '',`);
372
+ }
373
+ for (const v of model.secrets) {
374
+ lines.push(` ${varToKey(v)}: process.env.${v} || '', // secret — provide via environment only`);
375
+ }
376
+ lines.push('};');
377
+ lines.push('');
378
+ if (ts)
379
+ lines.push('export type TestData = typeof testData;');
380
+ lines.push('');
381
+ return lines.join('\n');
382
+ }
383
+ function smokeTest(model, ts) {
384
+ const classes = [...new Set(model.steps.map((s) => s.screen))];
385
+ const lines = [];
386
+ lines.push('// Generated by Swipium. Smoke test driving the recorded flow through the screen objects.');
387
+ for (const c of classes) {
388
+ lines.push(`import { ${c} } from '../src/screens/${c}.js';`);
389
+ }
390
+ lines.push(`import { testData } from '../src/data/testData.js';`);
391
+ lines.push('');
392
+ lines.push(`describe('${model.testName} smoke', () => {`);
393
+ // instantiate one screen object per class.
394
+ const inst = new Map();
395
+ for (const c of classes) {
396
+ const v = c[0].toLowerCase() + c.slice(1);
397
+ inst.set(c, v);
398
+ }
399
+ lines.push(` it('completes the recorded flow', async () => {`);
400
+ for (const [c, v] of inst)
401
+ lines.push(` const ${v} = new ${c}();`);
402
+ for (const s of model.steps) {
403
+ const v = inst.get(s.screen);
404
+ lines.push(' ' + stepCall(v, s));
405
+ }
406
+ lines.push(' });');
407
+ lines.push('});');
408
+ lines.push('');
409
+ return lines.join('\n');
410
+ }
411
+ function stepCall(screenVar, s) {
412
+ if (s.element) {
413
+ const name = methodName(s.action, s.element);
414
+ if (s.action === 'inputText') {
415
+ const arg = s.varName ? `testData.${varToKey(s.varName)}` : JSON.stringify(s.text ?? '');
416
+ return `await ${screenVar}.${name}(${arg});`;
417
+ }
418
+ return `await ${screenVar}.${name}();`;
419
+ }
420
+ // No element: generic BaseScreen helpers.
421
+ switch (s.action) {
422
+ case 'tapAt':
423
+ return `await ${screenVar}.tapAt(${s.coords?.[0] ?? 0}, ${s.coords?.[1] ?? 0}); // coordinate fallback — non-release-grade`;
424
+ case 'inputText': {
425
+ const arg = s.varName ? `testData.${varToKey(s.varName)}` : JSON.stringify(s.text ?? '');
426
+ return `await ${screenVar}.type({ android: '' }, ${arg}); // focused-field input`;
427
+ }
428
+ case 'press':
429
+ return `await ${screenVar}.pressKey(${JSON.stringify(s.key ?? 'back')});`;
430
+ case 'swipe':
431
+ return `await ${screenVar}.swipe(${JSON.stringify(s.direction ?? 'up')});`;
432
+ case 'openUrl':
433
+ return `await ${screenVar}.openUrl(${JSON.stringify(s.url ?? '')});`;
434
+ case 'assertVisible':
435
+ return `await ${screenVar}.assertTextVisible(${JSON.stringify(s.text ?? '')});`;
436
+ default:
437
+ return `// unsupported step: ${s.action}`;
438
+ }
439
+ }
440
+ //# sourceMappingURL=jsEmitter.js.map