@xenon-device-management/xenon 2.14.0 → 2.16.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 (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.OLLAMA_URL = exports.PROVIDERS = void 0;
16
+ exports.useFakeAiProviders = useFakeAiProviders;
17
+ const axios_1 = __importDefault(require("axios"));
18
+ const sinon_1 = __importDefault(require("sinon"));
19
+ const config_1 = require("../../src/config");
20
+ exports.PROVIDERS = ['gemini', 'openai', 'anthropic', 'ollama'];
21
+ const AI_SETTINGS = [
22
+ 'aiProvider',
23
+ 'aiModel',
24
+ 'aiBaseUrl',
25
+ 'geminiApiKey',
26
+ 'openaiApiKey',
27
+ 'anthropicApiKey',
28
+ 'geminiModel',
29
+ 'openaiModel',
30
+ 'anthropicModel',
31
+ 'ollamaModel',
32
+ ];
33
+ exports.OLLAMA_URL = 'http://ollama.test:11434';
34
+ function json(status, body) {
35
+ return new Response(JSON.stringify(body), {
36
+ status,
37
+ // The OpenAI and Anthropic SDKs retry a 429 after a pause unless told not to.
38
+ headers: { 'content-type': 'application/json', 'x-should-retry': 'false' },
39
+ });
40
+ }
41
+ function okBody(provider, text) {
42
+ switch (provider) {
43
+ case 'gemini':
44
+ return {
45
+ candidates: [
46
+ { content: { role: 'model', parts: [{ text }] }, finishReason: 'STOP', index: 0 },
47
+ ],
48
+ };
49
+ case 'openai':
50
+ return {
51
+ id: 'chatcmpl-test',
52
+ object: 'chat.completion',
53
+ created: 0,
54
+ model: 'test',
55
+ choices: [
56
+ { index: 0, message: { role: 'assistant', content: text }, finish_reason: 'stop' },
57
+ ],
58
+ };
59
+ case 'anthropic':
60
+ return {
61
+ id: 'msg_test',
62
+ type: 'message',
63
+ role: 'assistant',
64
+ model: 'test',
65
+ content: [{ type: 'text', text }],
66
+ stop_reason: 'end_turn',
67
+ usage: { input_tokens: 1, output_tokens: 1 },
68
+ };
69
+ default:
70
+ return { response: text };
71
+ }
72
+ }
73
+ function errorBody(provider, status, said) {
74
+ const message = said !== null && said !== void 0 ? said : (status === 429 ? 'Resource has been exhausted (e.g. check quota).' : `HTTP ${status}`);
75
+ switch (provider) {
76
+ case 'gemini':
77
+ return { error: { code: status, message, status: 'RESOURCE_EXHAUSTED' } };
78
+ case 'anthropic':
79
+ return { type: 'error', error: { type: 'rate_limit_error', message } };
80
+ default:
81
+ return { error: { message, type: 'requests', code: 'rate_limit_exceeded' } };
82
+ }
83
+ }
84
+ function aborted() {
85
+ const err = new Error('This operation was aborted');
86
+ err.name = 'AbortError';
87
+ return err;
88
+ }
89
+ /** Resolves never, and rejects as fetch does when the request's signal aborts. */
90
+ function hangUntilAborted(signal) {
91
+ return new Promise((_, reject) => {
92
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
93
+ return reject(aborted());
94
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => reject(aborted()));
95
+ });
96
+ }
97
+ function useFakeAiProviders() {
98
+ let saved = {};
99
+ let savedMockEnv;
100
+ let provider = 'gemini';
101
+ let current = { status: 500 };
102
+ let ollamaUp = true;
103
+ const calls = [];
104
+ const sandbox = sinon_1.default.createSandbox();
105
+ beforeEach(() => {
106
+ saved = Object.fromEntries(AI_SETTINGS.map((k) => [k, config_1.config[k]]));
107
+ savedMockEnv = process.env.GEMINI_API_KEY;
108
+ // 'mock' would put the service in its built-in mock mode.
109
+ if (process.env.GEMINI_API_KEY === 'mock')
110
+ delete process.env.GEMINI_API_KEY;
111
+ for (const k of AI_SETTINGS)
112
+ config_1.config[k] = undefined;
113
+ config_1.config.aiProvider = 'gemini';
114
+ calls.length = 0;
115
+ current = { status: 500 };
116
+ ollamaUp = true;
117
+ sandbox.stub(globalThis, 'fetch').callsFake(((_url, init) => __awaiter(this, void 0, void 0, function* () {
118
+ var _a, _b;
119
+ const signal = (_a = init === null || init === void 0 ? void 0 : init.signal) !== null && _a !== void 0 ? _a : undefined;
120
+ calls.push({ provider, body: String((_b = init === null || init === void 0 ? void 0 : init.body) !== null && _b !== void 0 ? _b : ''), signal });
121
+ if (current === 'hang')
122
+ return hangUntilAborted(signal);
123
+ if ('text' in current)
124
+ return json(200, okBody(provider, current.text));
125
+ return json(current.status, errorBody(provider, current.status, current.message));
126
+ })));
127
+ // Ollama: its health check answers, then the generate call as told.
128
+ sandbox.stub(axios_1.default, 'get').callsFake(((url) => __awaiter(this, void 0, void 0, function* () {
129
+ if (url.startsWith(exports.OLLAMA_URL)) {
130
+ if (ollamaUp)
131
+ return { status: 200, data: { models: [] } };
132
+ throw Object.assign(new Error(`connect ECONNREFUSED ${exports.OLLAMA_URL}`), {
133
+ code: 'ECONNREFUSED',
134
+ });
135
+ }
136
+ throw new Error(`no network in tests: GET ${url}`);
137
+ })));
138
+ sandbox.stub(axios_1.default, 'post').callsFake(((url, body, cfg) => __awaiter(this, void 0, void 0, function* () {
139
+ if (!url.startsWith(exports.OLLAMA_URL))
140
+ throw new Error(`no network in tests: POST ${url}`);
141
+ const signal = cfg === null || cfg === void 0 ? void 0 : cfg.signal;
142
+ calls.push({ provider: 'ollama', body: JSON.stringify(body), signal });
143
+ if (current === 'hang')
144
+ return hangUntilAborted(signal);
145
+ if ('text' in current)
146
+ return { status: 200, data: okBody('ollama', current.text) };
147
+ const err = new Error(`Request failed with status code ${current.status}`);
148
+ err.response = {
149
+ status: current.status,
150
+ data: errorBody('ollama', current.status, current.message),
151
+ };
152
+ throw err;
153
+ })));
154
+ });
155
+ afterEach(() => {
156
+ sandbox.restore();
157
+ for (const k of AI_SETTINGS)
158
+ config_1.config[k] = saved[k];
159
+ if (savedMockEnv === undefined)
160
+ delete process.env.GEMINI_API_KEY;
161
+ else
162
+ process.env.GEMINI_API_KEY = savedMockEnv;
163
+ });
164
+ return {
165
+ calls,
166
+ use(name, opts = {}) {
167
+ provider = name;
168
+ const model = `test-${name}-${Math.random().toString(36).slice(2)}`;
169
+ const key = opts.key === false ? undefined : 'test-key';
170
+ config_1.config.aiProvider = name;
171
+ if (name === 'gemini')
172
+ Object.assign(config_1.config, { geminiApiKey: key, geminiModel: model });
173
+ if (name === 'openai')
174
+ Object.assign(config_1.config, { openaiApiKey: key, openaiModel: model });
175
+ if (name === 'anthropic')
176
+ Object.assign(config_1.config, { anthropicApiKey: key, anthropicModel: model });
177
+ if (name === 'ollama')
178
+ Object.assign(config_1.config, { aiBaseUrl: exports.OLLAMA_URL, ollamaModel: model });
179
+ return { model };
180
+ },
181
+ answer(answer) {
182
+ current = answer;
183
+ },
184
+ ollamaUp(up) {
185
+ ollamaUp = up;
186
+ },
187
+ };
188
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.startFakeProxy = startFakeProxy;
16
+ const http_1 = __importDefault(require("http"));
17
+ const net_1 = __importDefault(require("net"));
18
+ /**
19
+ * An HTTP proxy on 127.0.0.1 that forwards what it is sent and notes it: a
20
+ * request in absolute form (http-proxy-agent, axios) is passed on to its
21
+ * target, a CONNECT (https-proxy-agent, so WebSockets) becomes a tunnel, or
22
+ * is refused with `refuseConnect`. A spec can then tell a call that went
23
+ * through the proxy from one that went straight to the target.
24
+ */
25
+ function startFakeProxy() {
26
+ return __awaiter(this, arguments, void 0, function* (opts = {}) {
27
+ const seen = [];
28
+ const authorizations = [];
29
+ const sockets = new Set();
30
+ const resolve = (host) => { var _a, _b; return (_b = (_a = opts.hosts) === null || _a === void 0 ? void 0 : _a[host]) !== null && _b !== void 0 ? _b : host; };
31
+ const server = http_1.default.createServer((req, res) => {
32
+ var _a, _b;
33
+ seen.push(`${req.method} ${req.url}`);
34
+ authorizations.push(String((_a = req.headers['proxy-authorization']) !== null && _a !== void 0 ? _a : ''));
35
+ let target;
36
+ try {
37
+ target = new URL((_b = req.url) !== null && _b !== void 0 ? _b : '');
38
+ }
39
+ catch (_c) {
40
+ res.writeHead(400).end();
41
+ return;
42
+ }
43
+ const upstream = http_1.default.request(Object.assign(Object.assign({}, urlParts(target)), { hostname: resolve(target.hostname), method: req.method, headers: req.headers }), (answer) => {
44
+ var _a;
45
+ res.writeHead((_a = answer.statusCode) !== null && _a !== void 0 ? _a : 502, answer.headers);
46
+ answer.pipe(res);
47
+ });
48
+ upstream.on('error', () => res.writeHead(502).end());
49
+ req.pipe(upstream);
50
+ });
51
+ server.on('connect', (req, client, head) => {
52
+ var _a;
53
+ seen.push(`CONNECT ${req.url}`);
54
+ authorizations.push(String((_a = req.headers['proxy-authorization']) !== null && _a !== void 0 ? _a : ''));
55
+ if (opts.ignoreConnect) {
56
+ sockets.add(client);
57
+ client.on('error', () => client.destroy());
58
+ return;
59
+ }
60
+ if (opts.refuseConnect) {
61
+ client.end('HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n');
62
+ return;
63
+ }
64
+ const [host, port] = String(req.url).split(':');
65
+ const upstream = net_1.default.connect(Number(port), resolve(host), () => {
66
+ client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
67
+ if (head.length)
68
+ upstream.write(head);
69
+ upstream.pipe(client);
70
+ client.pipe(upstream);
71
+ });
72
+ sockets.add(upstream).add(client);
73
+ const drop = () => {
74
+ upstream.destroy();
75
+ client.destroy();
76
+ };
77
+ upstream.on('error', drop);
78
+ client.on('error', drop);
79
+ });
80
+ yield new Promise((resolve, reject) => {
81
+ server.once('error', reject);
82
+ server.listen(0, '127.0.0.1', () => resolve());
83
+ });
84
+ const { port } = server.address();
85
+ return {
86
+ url: `http://127.0.0.1:${port}`,
87
+ seen,
88
+ authorizations,
89
+ close: () => new Promise((resolve) => {
90
+ var _a;
91
+ for (const socket of sockets)
92
+ socket.destroy();
93
+ (_a = server.closeAllConnections) === null || _a === void 0 ? void 0 : _a.call(server);
94
+ server.close(() => resolve());
95
+ }),
96
+ };
97
+ });
98
+ }
99
+ function urlParts(url) {
100
+ return {
101
+ protocol: url.protocol,
102
+ hostname: url.hostname,
103
+ port: url.port,
104
+ path: `${url.pathname}${url.search}`,
105
+ };
106
+ }
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useLokiStores = useLokiStores;
4
+ exports.usePrismaStores = usePrismaStores;
4
5
  const device_store_1 = require("../../src/data-service/device-store");
6
+ const prisma_store_1 = require("../../src/data-service/prisma-store");
5
7
  // DeviceStoreFactory's cached stores, one per kind.
6
8
  const CACHES = ['_deviceStore', '_pendingSessionStore', '_cliArgsStore', '_healEtalonStore'];
7
9
  /**
@@ -39,3 +41,34 @@ function useLokiStores() {
39
41
  process.env.NODE_ENV = previousEnv;
40
42
  });
41
43
  }
44
+ /**
45
+ * The Prisma stores (what a server with SQLite runs) for the enclosing
46
+ * `describe`, however the file was started. Put the suite's database behind
47
+ * them first: `useScratchDatabase({ wholeSuite: true })`.
48
+ *
49
+ * `npm run test:all` sets NODE_ENV to 'test', so DeviceStoreFactory hands it
50
+ * Loki stores, while `npx mocha <file>` gets Prisma ones. A spec written
51
+ * against the Prisma store's queries then tested Loki in the full run, and its
52
+ * phones stayed in Loki's one collection for every later spec. This hands the
53
+ * factory new Prisma stores for the suite, then puts the old ones back. A
54
+ * module that took its store when it was imported (`grid.ts`) keeps that one.
55
+ *
56
+ * Call it inside a `describe`, before the hooks that use a store, never at the
57
+ * top of a file.
58
+ */
59
+ function usePrismaStores() {
60
+ const factory = device_store_1.DeviceStoreFactory;
61
+ const saved = {};
62
+ before(() => {
63
+ for (const key of CACHES)
64
+ saved[key] = factory[key];
65
+ factory._deviceStore = new prisma_store_1.PrismaDeviceStore();
66
+ factory._pendingSessionStore = new prisma_store_1.PrismaPendingSessionStore();
67
+ factory._cliArgsStore = new prisma_store_1.PrismaCLIArgsStore();
68
+ factory._healEtalonStore = new prisma_store_1.PrismaHealEtalonStore();
69
+ });
70
+ after(() => {
71
+ for (const key of CACHES)
72
+ factory[key] = saved[key];
73
+ });
74
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.startOtlpProbe = startOtlpProbe;
16
+ exports.attributeOf = attributeOf;
17
+ exports.resetOtelGlobals = resetOtelGlobals;
18
+ exports.flushOtelGlobals = flushOtelGlobals;
19
+ const http_1 = __importDefault(require("http"));
20
+ const api_1 = require("@opentelemetry/api");
21
+ const api_logs_1 = require("@opentelemetry/api-logs");
22
+ /**
23
+ * A stand-in OTLP/HTTP collector on 127.0.0.1. It answers every request 200
24
+ * and keeps what it was sent, so a spec can see where Xenon's telemetry went
25
+ * and in which encoding (Xenon's own exporters send OTLP/JSON; the ones the
26
+ * OTel SDK builds from OTEL_* variables send protobuf).
27
+ */
28
+ function startOtlpProbe() {
29
+ return __awaiter(this, void 0, void 0, function* () {
30
+ const requests = [];
31
+ const server = http_1.default.createServer((req, res) => {
32
+ const chunks = [];
33
+ req.on('data', (chunk) => chunks.push(chunk));
34
+ req.on('end', () => {
35
+ var _a, _b;
36
+ const body = Buffer.concat(chunks);
37
+ const contentType = String((_a = req.headers['content-type']) !== null && _a !== void 0 ? _a : '');
38
+ let json;
39
+ if (contentType.includes('json')) {
40
+ try {
41
+ json = JSON.parse(body.toString('utf8'));
42
+ }
43
+ catch (_c) {
44
+ json = undefined;
45
+ }
46
+ }
47
+ requests.push({ path: (_b = req.url) !== null && _b !== void 0 ? _b : '', contentType, body, json });
48
+ if (contentType.includes('json')) {
49
+ res.writeHead(200, { 'content-type': 'application/json' });
50
+ res.end('{}');
51
+ }
52
+ else {
53
+ res.writeHead(200, { 'content-type': 'application/x-protobuf' });
54
+ res.end();
55
+ }
56
+ });
57
+ });
58
+ yield new Promise((resolve, reject) => {
59
+ server.once('error', reject);
60
+ server.listen(0, '127.0.0.1', () => resolve());
61
+ });
62
+ const { port } = server.address();
63
+ return {
64
+ url: (path) => `http://127.0.0.1:${port}${path}`,
65
+ requests,
66
+ spans: () => requests.flatMap((r) => {
67
+ var _a, _b;
68
+ return ((_b = (_a = r.json) === null || _a === void 0 ? void 0 : _a.resourceSpans) !== null && _b !== void 0 ? _b : []).flatMap((rs) => { var _a; return ((_a = rs.scopeSpans) !== null && _a !== void 0 ? _a : []).flatMap((ss) => { var _a; return ((_a = ss.spans) !== null && _a !== void 0 ? _a : []); }); });
69
+ }),
70
+ close: () => new Promise((resolve) => {
71
+ var _a;
72
+ (_a = server.closeAllConnections) === null || _a === void 0 ? void 0 : _a.call(server);
73
+ server.close(() => resolve());
74
+ }),
75
+ };
76
+ });
77
+ }
78
+ /** A span attribute's value, whatever its OTLP/JSON type. */
79
+ function attributeOf(attributes, key) {
80
+ const found = attributes === null || attributes === void 0 ? void 0 : attributes.find((a) => a.key === key);
81
+ if (!found)
82
+ return undefined;
83
+ return Object.values(found.value)[0];
84
+ }
85
+ /**
86
+ * Takes every OTel global (tracer, meter and logger providers, context
87
+ * manager, propagator) off the API, so the next registration wins. The API
88
+ * keeps the first registration for the life of the process and refuses the
89
+ * rest: a spec that leaves one behind decides what every later spec exports.
90
+ */
91
+ function resetOtelGlobals() {
92
+ api_1.trace.disable();
93
+ api_1.metrics.disable();
94
+ api_logs_1.logs.disable();
95
+ api_1.context.disable();
96
+ api_1.propagation.disable();
97
+ }
98
+ /** Exports whatever the registered meter and logger providers hold now. */
99
+ function flushOtelGlobals() {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ var _a, _b;
102
+ const meterProvider = api_1.metrics.getMeterProvider();
103
+ yield ((_a = meterProvider.forceFlush) === null || _a === void 0 ? void 0 : _a.call(meterProvider));
104
+ const loggerProvider = api_logs_1.logs.getLoggerProvider();
105
+ yield ((_b = loggerProvider.forceFlush) === null || _b === void 0 ? void 0 : _b.call(loggerProvider));
106
+ });
107
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ios = exports.android = void 0;
13
+ exports.elementIdOf = elementIdOf;
14
+ exports.pageDriver = pageDriver;
15
+ const xmldom_1 = require("@xmldom/xmldom");
16
+ const xpath_1 = require("xpath");
17
+ /**
18
+ * Page sources shaped like the drivers' own (UiAutomator2's `hierarchy`,
19
+ * XCUITest's `AppiumAUT`), and a driver that answers finds by running the
20
+ * XPath on them, as the real drivers do, for specs that check which element a
21
+ * healed locator selects. No phone.
22
+ */
23
+ const W3C_ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf';
24
+ const attrs = (o) => Object.entries(o)
25
+ .map(([k, v]) => `${k}="${v}"`)
26
+ .join(' ');
27
+ function androidNode(tag, index, bounds, extra = {}, children) {
28
+ const a = attrs(Object.assign({ index, package: 'com.example.shop', class: tag, text: '', 'resource-id': '', 'content-desc': '', checkable: 'false', checked: 'false', clickable: 'false', enabled: 'true', focusable: 'false', focused: 'false', 'long-clickable': 'false', password: 'false', scrollable: 'false', selected: 'false', bounds, displayed: 'true' }, extra));
29
+ return children === undefined ? `<${tag} ${a} />` : `<${tag} ${a}>${children}</${tag}>`;
30
+ }
31
+ exports.android = {
32
+ button: (index, id, text, bounds) => androidNode('android.widget.Button', index, bounds, {
33
+ 'resource-id': `com.example.shop:id/${id}`,
34
+ text,
35
+ 'content-desc': text,
36
+ clickable: 'true',
37
+ focusable: 'true',
38
+ }),
39
+ field: (index, id, text, bounds) => androidNode('android.widget.EditText', index, bounds, {
40
+ 'resource-id': `com.example.shop:id/${id}`,
41
+ text,
42
+ clickable: 'true',
43
+ focusable: 'true',
44
+ }),
45
+ text: (index, id, text, bounds) => androidNode('android.widget.TextView', index, bounds, {
46
+ 'resource-id': `com.example.shop:id/${id}`,
47
+ text,
48
+ }),
49
+ image: (index, id, description, bounds) => androidNode('android.widget.ImageView', index, bounds, {
50
+ 'resource-id': `com.example.shop:id/${id}`,
51
+ 'content-desc': description,
52
+ }),
53
+ /** A clickable row holding other elements, as list rows and compound buttons are. */
54
+ row: (index, id, bounds, children) => androidNode('android.widget.LinearLayout', index, bounds, { 'resource-id': `com.example.shop:id/${id}`, clickable: 'true', focusable: 'true' }, children),
55
+ layout: (index, id, bounds, children) => androidNode('android.widget.LinearLayout', index, bounds, { 'resource-id': id ? `com.example.shop:id/${id}` : '' }, children),
56
+ page: (children) => "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
57
+ '<hierarchy index="0" class="hierarchy" rotation="0" width="1080" height="2220">' +
58
+ androidNode('android.widget.FrameLayout', 0, '[0,0][1080,2220]', {}, children) +
59
+ '</hierarchy>',
60
+ };
61
+ function iosNode(type, a, children = '') {
62
+ return `<${type} ${attrs(Object.assign({ type, enabled: 'true', visible: 'true', accessible: 'true' }, a))}>${children}</${type}>`;
63
+ }
64
+ exports.ios = {
65
+ button: (name, label, y) => iosNode('XCUIElementTypeButton', { name, label, x: 20, y, width: 350, height: 44 }),
66
+ field: (name, value, y) => iosNode('XCUIElementTypeTextField', { name, value, x: 20, y, width: 350, height: 44 }),
67
+ group: (children) => iosNode('XCUIElementTypeOther', { x: 0, y: 100, width: 390, height: 600 }, children),
68
+ page: (children) => '<?xml version="1.0" encoding="UTF-8"?><AppiumAUT>' +
69
+ iosNode('XCUIElementTypeApplication', { name: 'Shop', label: 'Shop', x: 0, y: 0, width: 390, height: 844 }, iosNode('XCUIElementTypeWindow', { x: 0, y: 0, width: 390, height: 844 }, children)) +
70
+ '</AppiumAUT>',
71
+ };
72
+ /** The element id the fake driver gives an element: its resource-id or name. */
73
+ function elementIdOf(el) {
74
+ return `el:${el.getAttribute('resource-id') || el.getAttribute('name') || el.tagName}`;
75
+ }
76
+ /**
77
+ * A driver showing `pageSource`: finds run the XPath on it and answer the
78
+ * first match, attributes and rects are the element's own. `show` changes the
79
+ * screen.
80
+ */
81
+ function pageDriver(pageSource) {
82
+ let doc = new xmldom_1.DOMParser().parseFromString(pageSource, 'text/xml');
83
+ let source = pageSource;
84
+ const known = new Map();
85
+ const select = (using, value) => {
86
+ if (using !== 'xpath')
87
+ throw new Error(`unsupported locator strategy ${using}`);
88
+ return (0, xpath_1.select)(value, doc).filter((n) => n.nodeType === 1);
89
+ };
90
+ const ref = (el) => {
91
+ const id = elementIdOf(el);
92
+ known.set(id, el);
93
+ return { ELEMENT: id, [W3C_ELEMENT_KEY]: id };
94
+ };
95
+ const element = (id) => {
96
+ const el = known.get(id);
97
+ if (!el)
98
+ throw new Error(`no element ${id}`);
99
+ return el;
100
+ };
101
+ return {
102
+ show(next) {
103
+ source = next;
104
+ doc = new xmldom_1.DOMParser().parseFromString(next, 'text/xml');
105
+ },
106
+ getPageSource() {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ return source;
109
+ });
110
+ },
111
+ findElement(using, value) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ const [first] = select(using, value);
114
+ if (!first) {
115
+ throw Object.assign(new Error('NoSuchElement: An element could not be located'), {
116
+ name: 'NoSuchElementError',
117
+ });
118
+ }
119
+ return ref(first);
120
+ });
121
+ },
122
+ findElements(using, value) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ return select(using, value).map(ref);
125
+ });
126
+ },
127
+ caps: { platformName: /<XCUIElementType/.test(pageSource) ? 'iOS' : 'Android' },
128
+ /**
129
+ * As the drivers answer: getAttribute(name, elementId). UiAutomator2
130
+ * answers String(value), so an attribute the element hasn't set is
131
+ * "null"; WebDriverAgent answers null.
132
+ */
133
+ getAttribute(name, id) {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ const value = element(id).getAttribute(name);
136
+ if (value)
137
+ return value;
138
+ return /<XCUIElementType/.test(source) ? null : 'null';
139
+ });
140
+ },
141
+ getName(id) {
142
+ return __awaiter(this, void 0, void 0, function* () {
143
+ return element(id).tagName;
144
+ });
145
+ },
146
+ getElementRect(id) {
147
+ return __awaiter(this, void 0, void 0, function* () {
148
+ var _a;
149
+ const el = element(id);
150
+ const bounds = el.getAttribute('bounds');
151
+ if (bounds) {
152
+ const [x1, y1, x2, y2] = ((_a = bounds.match(/\d+/g)) !== null && _a !== void 0 ? _a : []).map(Number);
153
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
154
+ }
155
+ return {
156
+ x: Number(el.getAttribute('x')),
157
+ y: Number(el.getAttribute('y')),
158
+ width: Number(el.getAttribute('width')),
159
+ height: Number(el.getAttribute('height')),
160
+ };
161
+ });
162
+ },
163
+ };
164
+ }