@xenon-device-management/xenon 2.0.0 → 2.2.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 (289) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +3 -4
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
  5. package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
  69. package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
  70. package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +11 -104
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/routers/nodePhoneControl.js +301 -0
  84. package/lib/src/app/ws/h264StreamWs.js +43 -12
  85. package/lib/src/app/ws/logcatWs.js +48 -14
  86. package/lib/src/app/ws/nodeSocketRelay.js +167 -0
  87. package/lib/src/app/ws/upgradeRouter.js +215 -0
  88. package/lib/src/dashboard/event-manager.js +7 -6
  89. package/lib/src/data-service/device-service.js +152 -49
  90. package/lib/src/data-service/device-store.js +92 -10
  91. package/lib/src/data-service/deviceClaims.js +116 -0
  92. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  93. package/lib/src/data-service/prisma-store.js +102 -31
  94. package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
  95. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  96. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  97. package/lib/src/device-managers/NodeDevices.js +7 -2
  98. package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
  99. package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
  100. package/lib/src/device-managers/android/previewHold.js +2 -2
  101. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  102. package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
  103. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  104. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  105. package/lib/src/device-utils.js +78 -24
  106. package/lib/src/gateway/createHandoff.js +73 -0
  107. package/lib/src/gateway/defaultGateway.js +80 -0
  108. package/lib/src/gateway/forwardToNode.js +147 -0
  109. package/lib/src/gateway/hubSessionToken.js +356 -0
  110. package/lib/src/gateway/internalCall.js +90 -0
  111. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  112. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  113. package/lib/src/gateway/sessionCreate.js +136 -0
  114. package/lib/src/gateway/sessionGateway.js +201 -0
  115. package/lib/src/gateway/sessionLocator.js +100 -0
  116. package/lib/src/generated/client/edge.js +7 -4
  117. package/lib/src/generated/client/index-browser.js +4 -1
  118. package/lib/src/generated/client/index.d.ts +112 -1
  119. package/lib/src/generated/client/index.js +7 -4
  120. package/lib/src/generated/client/package.json +1 -1
  121. package/lib/src/generated/client/schema.prisma +9 -0
  122. package/lib/src/generated/client/wasm.js +4 -1
  123. package/lib/src/helpers/index.js +5 -1
  124. package/lib/src/logger.js +5 -0
  125. package/lib/src/middleware/authMiddleware.js +80 -0
  126. package/lib/src/middleware/commandAuth.js +31 -17
  127. package/lib/src/middleware/csrfMiddleware.js +3 -2
  128. package/lib/src/middleware/deviceAccessGuard.js +4 -1
  129. package/lib/src/middleware/sessionListingFilter.js +153 -0
  130. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  131. package/lib/src/plugin.js +27 -1
  132. package/lib/src/prisma.js +2 -1
  133. package/lib/src/services/DeviceReconciler.js +5 -2
  134. package/lib/src/services/OrphanSweeper.js +12 -19
  135. package/lib/src/services/PortAllocator.js +75 -16
  136. package/lib/src/services/ServerManager.js +63 -30
  137. package/lib/src/services/SessionLifecycleService.js +335 -76
  138. package/lib/src/services/ShutdownCoordinator.js +7 -1
  139. package/lib/src/services/SocketServer.js +11 -2
  140. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  141. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  142. package/lib/src/services/device-access/ticketActorAccess.js +3 -1
  143. package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
  144. package/lib/src/services/lease/LeaseService.js +19 -5
  145. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  146. package/lib/src/services/session/sessionCredentials.js +7 -59
  147. package/lib/src/sessions/CloudSession.js +12 -0
  148. package/lib/src/sessions/LocalSession.js +52 -26
  149. package/lib/src/sessions/RemoteSession.js +116 -9
  150. package/lib/src/sessions/SessionManager.js +49 -6
  151. package/lib/src/sessions/appiumUmbrella.js +32 -0
  152. package/lib/test/helpers/appium-umbrella.js +84 -0
  153. package/lib/test/helpers/fake-device-table.js +97 -0
  154. package/lib/test/helpers/loki-stores.js +41 -0
  155. package/lib/test/helpers/loopbackRequest.js +77 -0
  156. package/lib/test/helpers/own-node-id.js +29 -0
  157. package/lib/test/helpers/scratch-database.js +100 -0
  158. package/lib/test/helpers/scratch-port-leases.js +119 -0
  159. package/lib/test/helpers/test-container.js +35 -27
  160. package/lib/test/integration/androidDevices.spec.js +5 -4
  161. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  162. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  163. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  164. package/lib/test/setup/scratch-default-database.js +32 -0
  165. package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
  166. package/lib/test/unit/DeviceModel.spec.js +9 -9
  167. package/lib/test/unit/HealingCascade.test.js +5 -1
  168. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  169. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  170. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  171. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  172. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  173. package/lib/test/unit/android-h264-service.spec.js +24 -0
  174. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  175. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  176. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  177. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  178. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  179. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  180. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  181. package/lib/test/unit/authMiddleware.test.js +3 -0
  182. package/lib/test/unit/bug-report/route.spec.js +9 -9
  183. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  184. package/lib/test/unit/clipboard-route.spec.js +22 -8
  185. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  186. package/lib/test/unit/command-auth.spec.js +31 -31
  187. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  188. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  189. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  190. package/lib/test/unit/create-error-message.spec.js +43 -0
  191. package/lib/test/unit/create-handoff.spec.js +86 -0
  192. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  193. package/lib/test/unit/device-access-guard.spec.js +66 -28
  194. package/lib/test/unit/device-service.spec.js +4 -0
  195. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  196. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  197. package/lib/test/unit/device-team-guard.spec.js +20 -20
  198. package/lib/test/unit/device-utils.spec.js +8 -0
  199. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  200. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  201. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  202. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  203. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  204. package/lib/test/unit/helpers.spec.js +27 -0
  205. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  206. package/lib/test/unit/hub-create-once.spec.js +190 -0
  207. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  208. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  209. package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
  210. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  211. package/lib/test/unit/hub-node-race.spec.js +426 -0
  212. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  213. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  214. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  215. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  216. package/lib/test/unit/hub-session-token.spec.js +265 -0
  217. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  218. package/lib/test/unit/install-repository-app-team.spec.js +6 -2
  219. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  220. package/lib/test/unit/internal-call.spec.js +181 -0
  221. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  222. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  223. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  224. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  225. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  226. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  227. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  228. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  229. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
  230. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  231. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  232. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  233. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  234. package/lib/test/unit/logcat-ws.spec.js +12 -1
  235. package/lib/test/unit/loopback-request.spec.js +81 -0
  236. package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
  237. package/lib/test/unit/node-create-caps.spec.js +54 -0
  238. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  239. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  240. package/lib/test/unit/node-phone-control.spec.js +131 -0
  241. package/lib/test/unit/node-socket-relay.spec.js +357 -0
  242. package/lib/test/unit/omni-vision.spec.js +7 -3
  243. package/lib/test/unit/plugin.spec.js +45 -4
  244. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  245. package/lib/test/unit/profile-router.test.js +11 -11
  246. package/lib/test/unit/queue-requester.spec.js +5 -0
  247. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  248. package/lib/test/unit/queue-visibility.spec.js +1 -1
  249. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  250. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  251. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  252. package/lib/test/unit/recording-store.spec.js +9 -3
  253. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  254. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  255. package/lib/test/unit/remote-session-create.spec.js +130 -0
  256. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  257. package/lib/test/unit/reset-link.test.js +14 -14
  258. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  259. package/lib/test/unit/session-assets.spec.js +9 -9
  260. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  261. package/lib/test/unit/session-create-layer.spec.js +255 -0
  262. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  263. package/lib/test/unit/session-credentials.spec.js +0 -47
  264. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  265. package/lib/test/unit/session-locator.spec.js +250 -0
  266. package/lib/test/unit/session-node-calls.spec.js +174 -0
  267. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  268. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  269. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  270. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  271. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  272. package/lib/test/unit/stream-leave-route.spec.js +19 -7
  273. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  274. package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
  275. package/lib/test/unit/stream-stop-recording.spec.js +17 -5
  276. package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
  277. package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
  278. package/lib/test/unit/upgrade-router.spec.js +446 -0
  279. package/lib/test/unit/users-router.test.js +11 -8
  280. package/lib/test/unit/verify-credential.spec.js +4 -1
  281. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  282. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  283. package/lib/tsconfig.tsbuildinfo +1 -1
  284. package/package.json +3 -4
  285. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  286. package/prisma/schema.prisma +9 -0
  287. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  288. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  289. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -0,0 +1,100 @@
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.useScratchDatabase = useScratchDatabase;
16
+ const child_process_1 = require("child_process");
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const os_1 = __importDefault(require("os"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const sinon_1 = __importDefault(require("sinon"));
21
+ const client_1 = require("../../src/generated/client");
22
+ const prisma_1 = require("../../src/prisma");
23
+ // Every model the `prisma` wrapper exposes (src/prisma.ts MODEL_DELEGATES).
24
+ const MODELS = [
25
+ 'build',
26
+ 'session',
27
+ 'sessionLog',
28
+ 'log',
29
+ 'profiling',
30
+ 'app',
31
+ 'device',
32
+ 'pendingSession',
33
+ 'cLIArgs',
34
+ 'webhookConfig',
35
+ 'webConfig',
36
+ 'locatorEtalon',
37
+ 'lease',
38
+ 'portLease',
39
+ 'apiKey',
40
+ 'selectorState',
41
+ 'user',
42
+ 'userSession',
43
+ 'passwordResetToken',
44
+ 'team',
45
+ 'teamMember',
46
+ 'eventLog',
47
+ 'project',
48
+ 'recording',
49
+ 'bookmark',
50
+ 'annotation',
51
+ ];
52
+ /**
53
+ * Back the whole `prisma` wrapper with a scratch SQLite file for the enclosing
54
+ * `describe`, so the real queries (the device store, the session lifecycle,
55
+ * SessionOwnerResolver) run against a real database that is not the server's.
56
+ * Like useScratchPortLeases, for every model.
57
+ *
58
+ * Call it inside a `describe`, never at the top of a file. The database is
59
+ * migrated once per suite; the stubs are made before each test and removed
60
+ * after it, so a spec's own `sinon.restore()` can't leave the next test on
61
+ * the real database. Tests empty the tables they use.
62
+ */
63
+ function useScratchDatabase() {
64
+ const ctx = {};
65
+ const sandbox = sinon_1.default.createSandbox();
66
+ let dbPath = '';
67
+ before(function () {
68
+ this.timeout(90000);
69
+ dbPath = path_1.default.join(os_1.default.tmpdir(), `xenon-scratch-${process.pid}-${Date.now()}.db`);
70
+ const url = `file:${dbPath}`;
71
+ (0, child_process_1.execSync)('npx prisma migrate deploy', {
72
+ cwd: path_1.default.resolve(__dirname, '../..'),
73
+ env: Object.assign(Object.assign({}, process.env), { DATABASE_URL: url }),
74
+ stdio: 'pipe',
75
+ });
76
+ ctx.db = new client_1.PrismaClient({ datasources: { db: { url } } });
77
+ });
78
+ beforeEach(() => {
79
+ for (const model of MODELS) {
80
+ const wrapper = prisma_1.prisma[model];
81
+ const delegate = ctx.db[model];
82
+ for (const [name, value] of Object.entries(wrapper)) {
83
+ if (typeof value !== 'function' || typeof delegate[name] !== 'function')
84
+ continue;
85
+ sandbox.stub(wrapper, name).callsFake((...args) => delegate[name](...args));
86
+ }
87
+ }
88
+ });
89
+ afterEach(() => sandbox.restore());
90
+ after(() => __awaiter(this, void 0, void 0, function* () {
91
+ sandbox.restore();
92
+ if (ctx.db)
93
+ yield ctx.db.$disconnect();
94
+ for (const f of [dbPath, `${dbPath}-journal`]) {
95
+ if (f && fs_1.default.existsSync(f))
96
+ fs_1.default.unlinkSync(f);
97
+ }
98
+ }));
99
+ return ctx;
100
+ }
@@ -0,0 +1,119 @@
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.useScratchPortLeases = useScratchPortLeases;
16
+ const child_process_1 = require("child_process");
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const net_1 = __importDefault(require("net"));
19
+ const os_1 = __importDefault(require("os"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const sinon_1 = __importDefault(require("sinon"));
22
+ const client_1 = require("../../src/generated/client");
23
+ const prisma_1 = require("../../src/prisma");
24
+ const METHODS = [
25
+ 'findUnique',
26
+ 'findFirst',
27
+ 'findMany',
28
+ 'create',
29
+ 'update',
30
+ 'upsert',
31
+ 'delete',
32
+ 'deleteMany',
33
+ ];
34
+ /**
35
+ * Below every OS's ephemeral range (macOS 49152-65535, Linux 32768-60999).
36
+ * The blocks used to come from 40000-60000, half of it inside macOS's range,
37
+ * where the suite's own outgoing connections take ports: now and then one
38
+ * held base+1 while a two-port range needed it, and PortAllocator answered
39
+ * "range exhausted" in the full run only.
40
+ */
41
+ const FIRST_BASE = 20000;
42
+ const BLOCKS = 128;
43
+ const USED_OFFSETS = [...Array(10).keys(), ...Array.from({ length: 10 }, (_, i) => 50 + i)];
44
+ function bindable(port, host) {
45
+ return new Promise((resolve) => {
46
+ const server = net_1.default.createServer();
47
+ server.once('error', (err) =>
48
+ // A host without IPv6 can't have a listener there either.
49
+ resolve(err.code === 'EADDRNOTAVAIL' || err.code === 'EAFNOSUPPORT'));
50
+ server.listen(port, host, () => server.close(() => resolve(true)));
51
+ });
52
+ }
53
+ function blockIsFree(base) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ for (const offset of USED_OFFSETS) {
56
+ if (!(yield bindable(base + offset, '0.0.0.0')))
57
+ return false;
58
+ if (!(yield bindable(base + offset, '::')))
59
+ return false;
60
+ }
61
+ return true;
62
+ });
63
+ }
64
+ function freeBlock() {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ let base = FIRST_BASE;
67
+ for (let attempt = 0; attempt < 20; attempt++) {
68
+ base = FIRST_BASE + Math.floor(Math.random() * BLOCKS) * 100;
69
+ if (yield blockIsFree(base))
70
+ return base;
71
+ }
72
+ return base;
73
+ });
74
+ }
75
+ /**
76
+ * Back `prisma.portLease` with a scratch SQLite file for the enclosing
77
+ * `describe`, so PortAllocator runs its real queries (unique constraint on
78
+ * `port` included) without touching the server's database.
79
+ *
80
+ * Call it inside a `describe`, never at the top of a file. The database is
81
+ * created once per suite; the table is emptied and the stubs are re-made
82
+ * before each test, so a spec's own `sinon.restore()` can't leave the next
83
+ * test pointed at the real database.
84
+ */
85
+ function useScratchPortLeases() {
86
+ const ctx = {};
87
+ const sandbox = sinon_1.default.createSandbox();
88
+ let dbPath = '';
89
+ before(function () {
90
+ this.timeout(60000);
91
+ dbPath = path_1.default.join(os_1.default.tmpdir(), `xenon-port-leases-${process.pid}-${Date.now()}.db`);
92
+ const url = `file:${dbPath}`;
93
+ (0, child_process_1.execSync)('npx prisma migrate deploy', {
94
+ cwd: path_1.default.resolve(__dirname, '../..'),
95
+ env: Object.assign(Object.assign({}, process.env), { DATABASE_URL: url }),
96
+ stdio: 'pipe',
97
+ });
98
+ ctx.db = new client_1.PrismaClient({ datasources: { db: { url } } });
99
+ });
100
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
101
+ yield ctx.db.portLease.deleteMany({});
102
+ ctx.base = yield freeBlock();
103
+ const wrapper = prisma_1.prisma.portLease;
104
+ for (const m of METHODS) {
105
+ sandbox.stub(wrapper, m).callsFake((...args) => ctx.db.portLease[m](...args));
106
+ }
107
+ }));
108
+ afterEach(() => sandbox.restore());
109
+ after(() => __awaiter(this, void 0, void 0, function* () {
110
+ sandbox.restore();
111
+ if (ctx.db)
112
+ yield ctx.db.$disconnect();
113
+ for (const f of [dbPath, `${dbPath}-journal`]) {
114
+ if (f && fs_1.default.existsSync(f))
115
+ fs_1.default.unlinkSync(f);
116
+ }
117
+ }));
118
+ return ctx;
119
+ }
@@ -23,20 +23,38 @@ const PluginContext_1 = require("../../src/PluginContext");
23
23
  const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
24
24
  const uuid_1 = require("uuid");
25
25
  const db_1 = require("../../src/data-service/db");
26
- const device_store_1 = require("../../src/data-service/device-store");
27
26
  const AndroidDeviceManager_1 = __importDefault(require("../../src/device-managers/AndroidDeviceManager"));
28
27
  const IOSDeviceManager_1 = __importDefault(require("../../src/device-managers/IOSDeviceManager"));
29
28
  const device_managers_1 = require("../../src/device-managers");
30
- const sinon_1 = __importDefault(require("sinon"));
31
29
  const IOSDiscoveryService_1 = require("../../src/device-managers/ios/IOSDiscoveryService");
32
- const sandbox = sinon_1.default.createSandbox();
30
+ const container_registration_1 = require("./container-registration");
33
31
  /**
34
- * Test utility to initialize the TypeDI Container with a PluginContext
35
- * configured for testing purposes.
32
+ * The classes setupTestContainer() builds. Each call makes new ones, with its
33
+ * own PluginContext; the ones already registered are put back by
34
+ * resetTestContainer().
35
+ */
36
+ const BUILT = [
37
+ PluginContext_1.PluginContext,
38
+ IOSDiscoveryService_1.IOSDiscoveryService,
39
+ AndroidDeviceManager_1.default,
40
+ IOSDeviceManager_1.default,
41
+ device_managers_1.XenonManager,
42
+ ];
43
+ /** What each setupTestContainer() call not yet undone replaced, newest last. */
44
+ const undo = [];
45
+ /**
46
+ * Registers a PluginContext configured for testing, and the device managers
47
+ * built with it. Undo it with resetTestContainer() in the same `describe`.
48
+ *
49
+ * Only the ids it builds are replaced. It used to Container.reset(), which
50
+ * discarded every singleton in the process, including the ones other specs
51
+ * and the server's own modules had built and still held.
36
52
  */
37
53
  function setupTestContainer(overrides) {
38
- // Reset container to ensure clean state between tests
39
- typedi_1.Container.reset();
54
+ undo.push((0, container_registration_1.saveRegistrations)('LocalStorage', ...BUILT));
55
+ // Unbuilt again, so the Container.get() calls below make new instances.
56
+ for (const id of BUILT)
57
+ typedi_1.Container.set({ id, type: id });
40
58
  const nodeId = (0, uuid_1.v4)();
41
59
  const port = 4723;
42
60
  const pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, overrides || {});
@@ -85,28 +103,18 @@ function createTestXenonManager(pluginArgs) {
85
103
  xenonManager.init();
86
104
  return xenonManager;
87
105
  }
106
+ /**
107
+ * Puts back every registration the setupTestContainer() calls since the last
108
+ * reset replaced, newest first, and empties the in-memory device database.
109
+ *
110
+ * It changes nothing else. It used to stub device discovery on the
111
+ * AndroidDeviceManager and IOSDiscoveryService prototypes and never restore
112
+ * the stubs, so every spec that ran later got no devices from discovery.
113
+ */
88
114
  function resetTestContainer() {
89
115
  return __awaiter(this, void 0, void 0, function* () {
90
- sandbox.restore();
91
- typedi_1.Container.reset();
92
- // Stub discovery methods to prevent background pollution
93
- sandbox.stub(AndroidDeviceManager_1.default.prototype, 'getDevices').resolves([]);
94
- sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'getDevices').resolves([]);
95
- // @ts-ignore - Stubbing internal methods to be extra safe
96
- sandbox.stub(AndroidDeviceManager_1.default.prototype, 'fetchAndroidDevices').resolves([]);
97
- // @ts-ignore
98
- sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'fetchLocalIOSDevices').resolves([]);
99
- // @ts-ignore
100
- sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'fetchLocalSimulators').resolves([]);
101
- // Clear DeviceStoreFactory static caches to prevent cross-test pollution
102
- // @ts-ignore - Accessing private static members for test cleanup
103
- device_store_1.DeviceStoreFactory._deviceStore = undefined;
104
- // @ts-ignore
105
- device_store_1.DeviceStoreFactory._pendingSessionStore = undefined;
106
- // @ts-ignore
107
- device_store_1.DeviceStoreFactory._cliArgsStore = undefined;
108
- // @ts-ignore
109
- device_store_1.DeviceStoreFactory._healEtalonStore = undefined;
116
+ for (const restore of undo.splice(0).reverse())
117
+ restore();
110
118
  yield db_1.XenonDatabase.reset();
111
119
  });
112
120
  }
@@ -49,6 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = __importStar(require("chai"));
50
50
  const db_1 = require("../../src/data-service/db");
51
51
  const device_utils_1 = require("../../src/device-utils");
52
+ const localDeviceHosts_1 = require("../../src/device-managers/localDeviceHosts");
52
53
  const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
53
54
  const ip_1 = __importDefault(require("ip"));
54
55
  const async_wait_until_1 = __importDefault(require("async-wait-until"));
@@ -73,7 +74,7 @@ describe('Android Test', () => {
73
74
  yield (0, device_utils_1.initializeStorage)();
74
75
  yield deviceManager.getDevices();
75
76
  const hub = pluginArgs.hub;
76
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
77
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
77
78
  yield (0, device_utils_1.cleanPendingSessions)(0);
78
79
  yield (0, device_service_1.unblockDeviceMatchingFilter)({});
79
80
  const capabilities = {
@@ -95,7 +96,7 @@ describe('Android Test', () => {
95
96
  it.skip('Allocate second free device and verify both the device state is busy in db', () => __awaiter(void 0, void 0, void 0, function* () {
96
97
  yield (0, device_utils_1.initializeStorage)();
97
98
  deviceManager = (0, test_container_1.createTestXenonManager)(Object.assign({}, pluginArgs, { platform: 'android' }));
98
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp);
99
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723));
99
100
  const capabilities = {
100
101
  alwaysMatch: {
101
102
  platformName: 'android',
@@ -108,7 +109,7 @@ describe('Android Test', () => {
108
109
  // wait until there are two devices and both are not offline
109
110
  let devices;
110
111
  (0, async_wait_until_1.default)(() => __awaiter(void 0, void 0, void 0, function* () {
111
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp);
112
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723));
112
113
  devices = yield deviceManager.getDevices();
113
114
  return devices.length === 2 && devices.every((device) => !device.offline);
114
115
  }));
@@ -120,7 +121,7 @@ describe('Android Test', () => {
120
121
  yield (0, device_utils_1.initializeStorage)();
121
122
  deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
122
123
  const hub = pluginArgs.hub;
123
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
124
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
124
125
  const capabilities = {
125
126
  alwaysMatch: {
126
127
  platformName: 'android',
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  require("reflect-metadata");
23
23
  const chai_1 = require("chai");
24
24
  const device_utils_1 = require("../../../src/device-utils");
25
+ const localDeviceHosts_1 = require("../../../src/device-managers/localDeviceHosts");
25
26
  const db_1 = require("../../../src/data-service/db");
26
27
  const node_simctl_1 = __importDefault(require("node-simctl"));
27
28
  const pluginArgs_1 = require("../../../src/data-service/pluginArgs");
@@ -56,7 +57,7 @@ function initDeviceFarm(iosDeviceType) {
56
57
  }));
57
58
  (0, chai_1.expect)(deviceManager.getMaxSessionCount()).to.be.eql(1);
58
59
  const hub = pluginArgs.hub;
59
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
60
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
60
61
  yield markSimulatorsAsBooted();
61
62
  yield (0, device_service_1.unblockDeviceMatchingFilter)({});
62
63
  yield (0, device_utils_1.cleanPendingSessions)(0);
@@ -155,7 +156,7 @@ describe('IOS Simulator Test', () => {
155
156
  yield (0, device_utils_1.initializeStorage)();
156
157
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
157
158
  const hub = pluginArgs.hub;
158
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
159
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
159
160
  yield markSimulatorsAsBooted();
160
161
  yield (0, device_service_1.unblockDeviceMatchingFilter)({});
161
162
  yield (0, device_utils_1.cleanPendingSessions)(0);
@@ -184,7 +185,7 @@ describe('IOS Simulator Test', () => {
184
185
  yield (0, device_utils_1.initializeStorage)();
185
186
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
186
187
  const hub = pluginArgs.hub;
187
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
188
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
188
189
  yield markSimulatorsAsBooted();
189
190
  yield (0, device_service_1.unblockDeviceMatchingFilter)({});
190
191
  yield (0, device_utils_1.cleanPendingSessions)(0);
@@ -215,7 +216,7 @@ describe('IOS Simulator Test', () => {
215
216
  yield (0, device_utils_1.initializeStorage)();
216
217
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
217
218
  const hub = pluginArgs.hub;
218
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
219
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
219
220
  yield markSimulatorsAsBooted();
220
221
  yield (0, device_service_1.unblockDeviceMatchingFilter)({});
221
222
  yield (0, device_utils_1.cleanPendingSessions)(0);
@@ -265,7 +266,7 @@ describe('Boot simulator test', () => __awaiter(void 0, void 0, void 0, function
265
266
  yield (0, device_utils_1.initializeStorage)();
266
267
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
267
268
  const hub = pluginArgs.hub;
268
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp, hub);
269
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723), hub);
269
270
  const capabilities = {
270
271
  alwaysMatch: {
271
272
  platformName: 'iOS',
@@ -16,6 +16,7 @@ require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
17
  const ip_1 = __importDefault(require("ip"));
18
18
  const device_utils_1 = require("../../../src/device-utils");
19
+ const localDeviceHosts_1 = require("../../../src/device-managers/localDeviceHosts");
19
20
  const db_1 = require("../../../src/data-service/db");
20
21
  const IPluginArgs_1 = require("../../../src/interfaces/IPluginArgs");
21
22
  const device_service_1 = require("../../../src/data-service/device-service");
@@ -39,7 +40,7 @@ describe('IOS Test', () => {
39
40
  d.plugin['xenon'].iosDeviceType = 'real';
40
41
  });
41
42
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
42
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp);
43
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723));
43
44
  const capabilities = {
44
45
  alwaysMatch: {
45
46
  platformName: 'iOS',
@@ -57,7 +58,7 @@ describe('IOS Test', () => {
57
58
  it('Should throw error if the IPA does not match with device type real', () => __awaiter(void 0, void 0, void 0, function* () {
58
59
  yield (0, device_utils_1.initializeStorage)();
59
60
  const deviceManager = (0, test_container_1.createTestXenonManager)(pluginArgs);
60
- yield (0, device_utils_1.updateDeviceList)(pluginArgs.bindHostOrIp);
61
+ yield (0, device_utils_1.updateDeviceList)((0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, 4723));
61
62
  const capabilities = {
62
63
  alwaysMatch: {
63
64
  platformName: 'iOS',
@@ -65,10 +65,20 @@ describe('PrismaDeviceStore.addDevices (integration)', function () {
65
65
  (0, chai_1.expect)(yield row(NEW)).to.include({ name: 'Pixel 8', busy: false });
66
66
  (0, chai_1.expect)(yield row(KNOWN)).to.include({ busy: true });
67
67
  }));
68
- it('with mirror, writes every column the node sent', () => __awaiter(this, void 0, void 0, function* () {
69
- yield store.addDevices([phone(KNOWN, { busy: false, session_id: 'node-sess' })], {
70
- mirror: true,
68
+ it("as a node's report, records the node's busy apart and never writes the session", () => __awaiter(this, void 0, void 0, function* () {
69
+ yield prisma_1.prisma.device.updateMany({
70
+ where: { udid: KNOWN, host: HOST },
71
+ data: { claimSessionId: 'sess-1', claimedAt: 41 },
72
+ });
73
+ yield store.addDevices([phone(KNOWN, { busy: false, session_id: 'node-sess', sdk: '11' })], {
74
+ nodeReport: true,
75
+ });
76
+ (0, chai_1.expect)(yield row(KNOWN)).to.include({
77
+ sdk: '11',
78
+ nodeBusy: false,
79
+ busy: true,
80
+ session_id: 'sess-1',
81
+ claimSessionId: 'sess-1',
71
82
  });
72
- (0, chai_1.expect)(yield row(KNOWN)).to.include({ busy: false, session_id: 'node-sess' });
73
83
  }));
74
84
  });
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // Loaded with --require by `npm run test:all`, before any spec.
3
+ //
4
+ // src/config.ts reads DATABASE_URL once, at import time, and falls back to
5
+ // ~/.cache/xenon/xenon.db. Specs that don't stub the store (EventLogService,
6
+ // MetricsService and others writing in passing) used to write their rows into
7
+ // that file, the database a developer's own server uses. This points the whole
8
+ // run at a freshly migrated scratch file instead, and removes it at exit.
9
+ // Specs that need a database of their own still use useScratchDatabase().
10
+ /* eslint-disable @typescript-eslint/no-var-requires -- plain CommonJS, loaded before ts-node */
11
+ const { execSync } = require('child_process');
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const dbPath = path.join(os.tmpdir(), `xenon-test-run-${process.pid}-${Date.now()}.db`);
16
+ const url = `file:${dbPath}`;
17
+ execSync('npx prisma migrate deploy', {
18
+ cwd: path.resolve(__dirname, '../..'),
19
+ env: Object.assign(Object.assign({}, process.env), { DATABASE_URL: url }),
20
+ stdio: 'pipe',
21
+ });
22
+ process.env.DATABASE_URL = url;
23
+ process.on('exit', () => {
24
+ for (const f of [dbPath, `${dbPath}-journal`]) {
25
+ try {
26
+ fs.unlinkSync(f);
27
+ }
28
+ catch (_a) {
29
+ // already gone
30
+ }
31
+ }
32
+ });
@@ -14,13 +14,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  require("reflect-metadata");
16
16
  const sinon_1 = __importDefault(require("sinon"));
17
+ const AndroidDeviceManager_1 = require("../../src/device-managers/AndroidDeviceManager");
17
18
  const adbkit_1 = __importDefault(require("@devicefarmer/adbkit"));
18
19
  const appium_adb_1 = require("appium-adb");
19
20
  const chai_1 = require("chai");
20
21
  const test_container_1 = require("../helpers/test-container");
22
+ const scratch_database_1 = require("../helpers/scratch-database");
21
23
  const GetAdbOriginal_1 = require("./GetAdbOriginal");
22
24
  const sandbox = sinon_1.default.createSandbox();
23
25
  describe('Android Device Manager', () => {
26
+ // deviceInfo() leases the phone's ports through Prisma. Without a database
27
+ // of its own that was the developer's ~/.cache/xenon/xenon.db.
28
+ (0, scratch_database_1.useScratchDatabase)();
24
29
  let adb;
25
30
  beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
26
31
  sandbox.restore();
@@ -37,7 +42,10 @@ describe('Android Device Manager', () => {
37
42
  });
38
43
  }));
39
44
  afterEach(function () {
40
- sandbox.restore();
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ sandbox.restore();
47
+ yield (0, test_container_1.resetTestContainer)();
48
+ });
41
49
  });
42
50
  it('Android Device List to have added state', () => __awaiter(void 0, void 0, void 0, function* () {
43
51
  const androidDevices = (0, test_container_1.createTestAndroidManager)({ platform: 'android' });
@@ -158,6 +166,31 @@ describe('Android Device Manager', () => {
158
166
  (0, chai_1.expect)(devices.length).to.be.equal(1);
159
167
  (0, chai_1.expect)(devices[0]).to.have.property('udid', 'emulator-7777');
160
168
  }));
169
+ // A phone adb reports as plugged (and every phone connected when the server
170
+ // starts) went through onDeviceAdded with no androidDeviceType check: a
171
+ // hub set to emulators only listed a real phone until its next sync pruned
172
+ // it, and could hand it to a session meanwhile.
173
+ describe('a plugged phone', () => {
174
+ const plug = (androidDeviceType, real) => __awaiter(void 0, void 0, void 0, function* () {
175
+ const manager = (0, test_container_1.createTestAndroidManager)({ platform: 'android', androidDeviceType });
176
+ sandbox.stub(manager, 'waitBootComplete').resolves(true);
177
+ sandbox.stub(manager, 'isRealDevice').resolves(real);
178
+ // Stands for the rest of the add: the port lease, the row, the hub.
179
+ const deviceInfo = sandbox.stub(manager, 'deviceInfo').resolves(undefined);
180
+ yield manager.onDeviceAdded(adb, { id: 'plugged-1', type: 'device' });
181
+ return deviceInfo.called;
182
+ });
183
+ it('is ignored when the server doesn’t serve its kind', () => __awaiter(void 0, void 0, void 0, function* () {
184
+ (0, chai_1.expect)(yield plug('simulated', true), 'a real phone, emulators only').to.equal(false);
185
+ (0, chai_1.expect)(yield plug('real', false), 'an emulator, real phones only').to.equal(false);
186
+ }));
187
+ it('is added when the server serves its kind', () => __awaiter(void 0, void 0, void 0, function* () {
188
+ (0, chai_1.expect)(yield plug('simulated', false)).to.equal(true);
189
+ (0, chai_1.expect)(yield plug('real', true)).to.equal(true);
190
+ (0, chai_1.expect)(yield plug('both', true)).to.equal(true);
191
+ (0, chai_1.expect)(yield plug('both', false)).to.equal(true);
192
+ }));
193
+ });
161
194
  it('should handle device never completing boot', () => __awaiter(void 0, void 0, void 0, function* () {
162
195
  const androidDevices = (0, test_container_1.createTestAndroidManager)({ platform: 'android' });
163
196
  // @ts-expect-error - Accessing private member for testing
@@ -176,3 +209,25 @@ describe('Android Device Manager', () => {
176
209
  }).to.not.throw();
177
210
  }));
178
211
  });
212
+ // The one rule discovery and a plugged phone both apply.
213
+ describe('includesAndroidDevice', () => {
214
+ const real = { deviceType: 'real', state: 'device' };
215
+ const emulator = { deviceType: 'emulator', state: 'device' };
216
+ const offlineEmulator = { deviceType: 'emulator', state: 'offline' };
217
+ it('real: real phones only', () => {
218
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'real')).to.equal(true);
219
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'real')).to.equal(false);
220
+ });
221
+ it('simulated: emulators only, booted ones when bootedEmulators is on', () => {
222
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'simulated')).to.equal(false);
223
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'simulated')).to.equal(true);
224
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'simulated')).to.equal(true);
225
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'simulated', true)).to.equal(false);
226
+ });
227
+ it('both: everything, emulators booted when bootedEmulators is on', () => {
228
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'both')).to.equal(true);
229
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'both', true)).to.equal(true);
230
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'both', true)).to.equal(false);
231
+ (0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(Object.assign(Object.assign({}, real), { state: 'offline' }), 'both', true)).to.equal(true);
232
+ });
233
+ });
@@ -19,18 +19,20 @@ const device_service_1 = require("../../src/data-service/device-service");
19
19
  const devices_1 = require("./fixtures/devices");
20
20
  const chai_2 = require("chai");
21
21
  const device_service_2 = require("../../src/data-service/device-service");
22
- const sinon_1 = __importDefault(require("sinon"));
23
- chai_1.default.should();
24
- const sandbox = sinon_1.default.createSandbox();
25
22
  const test_container_1 = require("../helpers/test-container");
26
- describe('Model Test', () => __awaiter(void 0, void 0, void 0, function* () {
23
+ const loki_stores_1 = require("../helpers/loki-stores");
24
+ chai_1.default.should();
25
+ describe('Model Test', () => {
26
+ // The in-memory store this spec is about. Run on its own, without the
27
+ // NODE_ENV=test that test:all sets, it used the Prisma store instead: the
28
+ // developer's own database, whose tables it wrote to.
29
+ (0, loki_stores_1.useLokiStores)();
27
30
  before('Add device collection', () => __awaiter(void 0, void 0, void 0, function* () {
28
31
  yield (0, test_container_1.resetTestContainer)();
29
32
  (0, chai_2.expect)((yield db_1.XenonDatabase.DeviceModel).chain().find().data().length).to.be.equal(0);
30
33
  (0, chai_2.expect)(devices_1.deviceMock.length).to.be.greaterThanOrEqual(1);
31
34
  // console.log(`deviceMock length: ${deviceMock.length}`);
32
35
  (yield db_1.XenonDatabase.DeviceModel).insert(devices_1.deviceMock);
33
- (yield db_1.XenonDatabase.db).saveDatabase();
34
36
  // console.log(`result: ${result}`);
35
37
  // console.log(`device model length: ${deviceModel.find().length}`);
36
38
  (yield db_1.XenonDatabase.DeviceModel)
@@ -39,9 +41,7 @@ describe('Model Test', () => __awaiter(void 0, void 0, void 0, function* () {
39
41
  .data()
40
42
  .length.should.be.equal(devices_1.deviceMock.length);
41
43
  }));
42
- after('clean', () => {
43
- sandbox.restore();
44
- });
44
+ after('clean', () => (0, test_container_1.resetTestContainer)());
45
45
  it('Should remove device from old pool when new poll call does not have the device', () => __awaiter(void 0, void 0, void 0, function* () {
46
46
  const findDevice = (yield db_1.XenonDatabase.DeviceModel)
47
47
  .chain()
@@ -157,4 +157,4 @@ describe('Model Test', () => __awaiter(void 0, void 0, void 0, function* () {
157
157
  const updatedDeviceUdids = updatedDeviceList.map((device) => device.udid);
158
158
  addedDeviceUdids.forEach((udid) => updatedDeviceUdids.includes(udid).should.be.true);
159
159
  }));
160
- }));
160
+ });
@@ -9,12 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ require("reflect-metadata");
12
13
  const chai_1 = require("chai");
13
14
  const HealingOrchestrator_1 = require("../../src/services/healing/HealingOrchestrator");
14
15
  const types_1 = require("../../src/services/healing/types");
15
16
  const typedi_1 = require("typedi");
16
17
  const HealEtalonService_1 = require("../../src/services/healing/HealEtalonService");
17
18
  const HealedLocatorGenerator_1 = require("../../src/services/healing/HealedLocatorGenerator");
19
+ const container_registration_1 = require("../helpers/container-registration");
18
20
  // Mock Tesseract
19
21
  const TesseractMock = {
20
22
  recognize: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -38,8 +40,9 @@ describe('Healing Orchestrator - Cascade Strategy', () => {
38
40
  let orchestrator;
39
41
  let mockEtalonService;
40
42
  let mockGenerator;
43
+ let restoreContainer;
41
44
  beforeEach(() => {
42
- typedi_1.Container.reset();
45
+ restoreContainer = (0, container_registration_1.saveRegistrations)(HealEtalonService_1.HealEtalonService, HealedLocatorGenerator_1.HealedLocatorGenerator);
43
46
  mockEtalonService = {
44
47
  getSignature: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
45
48
  saveSignature: () => __awaiter(void 0, void 0, void 0, function* () { }),
@@ -49,6 +52,7 @@ describe('Healing Orchestrator - Cascade Strategy', () => {
49
52
  typedi_1.Container.set(HealedLocatorGenerator_1.HealedLocatorGenerator, mockGenerator);
50
53
  orchestrator = new HealingOrchestrator_1.HealingOrchestrator(mockEtalonService);
51
54
  });
55
+ afterEach(() => restoreContainer());
52
56
  it('should cascade to OCR (Tier 3) when Fuzzy XML (Tier 2) fails to find a high-confidence match', () => __awaiter(void 0, void 0, void 0, function* () {
53
57
  // 1. Setup a page source that has NO matching text or buttons for "Login"
54
58
  // This will cause Fuzzy XML Provider to return null (or score below 0.5 threshold)