@xenon-device-management/xenon 2.13.2 → 2.15.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 (350) hide show
  1. package/README.md +17 -8
  2. package/lib/package.json +5 -5
  3. package/lib/public/assets/{AnnotationOverlay-BXruClDk.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-qTS4LPLC.js → ApiKeyGate-C9OoAgg_.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
  6. package/lib/public/assets/{EmptyState-Cr17PdDv.js → EmptyState-DqWU2-vM.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Bvn08svv.js → FieldGroup-CveQ3xrA.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-DDSO8aDf.js → FilterMenu-BfKDSfHz.js} +1 -1
  9. package/lib/public/assets/{Menu-B-Qn2X3T.js → Menu-CtdCybzA.js} +1 -1
  10. package/lib/public/assets/{Modal-Cg9V5JYU.js → Modal-Bsvj1tm2.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-BTUs9VTW.js → RecordingPage-CRofOiyx.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-DYcfxITK.js → RecordingsPage-jNQHb2IR.js} +2 -2
  13. package/lib/public/assets/{SegmentedControl-Zy7suhT5.js → SegmentedControl-C8HtTha-.js} +1 -1
  14. package/lib/public/assets/{SettingCard-CWYmycoe.js → SettingCard-D8K1LZNo.js} +1 -1
  15. package/lib/public/assets/{Table-io9fwa98.js → Table-zNTX92Pn.js} +1 -1
  16. package/lib/public/assets/{activity-Bdf_uIWl.js → activity-CJ8LwZq9.js} +1 -1
  17. package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
  18. package/lib/public/assets/{api-keys-CPqtVpm3.js → api-keys-ECpo-zTo.js} +1 -1
  19. package/lib/public/assets/{apps-BgZan0H9.js → apps-BjGymyf3.js} +2 -2
  20. package/lib/public/assets/{arrow-left-B27ckB31.js → arrow-left-BWfCbeHP.js} +1 -1
  21. package/lib/public/assets/{arrow-right-DOF8ITCz.js → arrow-right-DC7UGf_n.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-eFG5KTFS.js → arrow-up-right-BOjwybDk.js} +1 -1
  23. package/lib/public/assets/{auth-shell-DlC3_27j.js → auth-shell-CxMTo9aH.js} +2 -2
  24. package/lib/public/assets/{builds-page-BuXpPfKo.js → builds-page-DOCm4HkA.js} +1 -1
  25. package/lib/public/assets/{button-fIW8owMI.js → button-Cg08-EMT.js} +1 -1
  26. package/lib/public/assets/{calendar-WNjmrF-a.js → calendar-CA1ikLJs.js} +1 -1
  27. package/lib/public/assets/{check-BdTh1Sq7.js → check-BMTaH72W.js} +1 -1
  28. package/lib/public/assets/{chevron-right-B4BKlsqk.js → chevron-right-B2QOv9Nx.js} +1 -1
  29. package/lib/public/assets/{circle-check-zJU80_EF.js → circle-check-CYXBoUqT.js} +1 -1
  30. package/lib/public/assets/{circle-x-5Uwquo0h.js → circle-x-DTwnSAnl.js} +1 -1
  31. package/lib/public/assets/{clock-BtUprDsZ.js → clock-C8_HViQP.js} +1 -1
  32. package/lib/public/assets/{copy-DGSbZI3u.js → copy-Be7LNMXV.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
  35. package/lib/public/assets/{download-WG6l7BgD.js → download-B56lwxWP.js} +1 -1
  36. package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
  37. package/lib/public/assets/h264Stream-C-OtmIuk.js +26 -0
  38. package/lib/public/assets/index-B9KQ6LsY.js +15 -0
  39. package/lib/public/assets/{index-DI1fGIyj.js → index-BOxe43p1.js} +5 -5
  40. package/lib/public/assets/{index-HDQVe1gi.js → index-CfxsUslp.js} +1 -1
  41. package/lib/public/assets/{input-BjDRKCea.js → input-CZHFjbSg.js} +1 -1
  42. package/lib/public/assets/{line-chart-C0zdgd0v.js → line-chart-Gl_Dz3X_.js} +1 -1
  43. package/lib/public/assets/{list-checks-BdW5CjM8.js → list-checks-CCgjeyGY.js} +1 -1
  44. package/lib/public/assets/{lock-Dy3L4I8E.js → lock-CkOwgDcx.js} +1 -1
  45. package/lib/public/assets/{login-BHbY9CLG.js → login-D5cc4w7M.js} +1 -1
  46. package/lib/public/assets/maintenance-settings-t7qr3fvX.js +6 -0
  47. package/lib/public/assets/{monitor-8xjkeHHi.js → monitor-DEJixKQO.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-CXdKMXQ8.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
  49. package/lib/public/assets/{network-Cj8JOKON.js → network-Dii9qdvk.js} +1 -1
  50. package/lib/public/assets/{overview-CTg7F0im.js → overview-rolAACrO.js} +1 -1
  51. package/lib/public/assets/{page-header--aTyJXq6.js → page-header-COQZxJfR.js} +1 -1
  52. package/lib/public/assets/{play-B-FodgHV.js → play-DrN1Se_R.js} +1 -1
  53. package/lib/public/assets/{plus-CsIk10Cq.js → plus-5aq-9uRx.js} +1 -1
  54. package/lib/public/assets/{profile-page-DkFKkMfl.js → profile-page-6i5kY6kF.js} +1 -1
  55. package/lib/public/assets/{reset-password-I2-zqLlI.js → reset-password-sp2IBrc7.js} +1 -1
  56. package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
  57. package/lib/public/assets/{select-DvCSv25z.js → select-Bpyn7rWO.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BLVfMJs_.js → selector-detail-redirect-BH74vmKv.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-oqadRokn.js → selector-health-page-Cw_g218H.js} +2 -2
  60. package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
  61. package/lib/public/assets/settings-C3uyGhyc.js +6 -0
  62. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  63. package/lib/public/assets/{stat-tile-DV9Ix-4O.js → stat-tile-xVqAKIlI.js} +1 -1
  64. package/lib/public/assets/{tablet-6b_SEN2X.js → tablet-BxUScAeO.js} +1 -1
  65. package/lib/public/assets/{teams-BjZyfuHh.js → teams-B5ljGkY2.js} +1 -1
  66. package/lib/public/assets/{trash-2-CTZpPSdd.js → trash-2-B701i8sy.js} +1 -1
  67. package/lib/public/assets/{upload-P3XzwQUb.js → upload-C2gr44RS.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-1cOCMf-z.js → use-builds-data-NeQOFEoy.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-DgF9w088.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
  70. package/lib/public/assets/{users-DMF9Mk5H.js → users-D_vv_yz4.js} +1 -1
  71. package/lib/public/assets/{users-CiNTKUiz.js → users-MY6eNe6s.js} +1 -1
  72. package/lib/public/assets/{video-off-CHp1KU_T.js → video-off-OK1lNZQn.js} +1 -1
  73. package/lib/public/assets/webhook-settings-BU-C9lBf.js +1 -0
  74. package/lib/public/assets/{zap-DRz0tDq5.js → zap-DO9ry1iz.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +23 -25
  77. package/lib/src/XenonCapabilityManager.js +22 -5
  78. package/lib/src/app/dashboardPluginLink.js +117 -0
  79. package/lib/src/app/index.js +13 -34
  80. package/lib/src/app/openapi/control.yaml +73 -20
  81. package/lib/src/app/openapi/grid.yaml +31 -7
  82. package/lib/src/app/openapi/identity.yaml +103 -31
  83. package/lib/src/app/openapi/platform.yaml +165 -39
  84. package/lib/src/app/openapi/sessions.yaml +10 -3
  85. package/lib/src/app/routers/apikeys.js +14 -2
  86. package/lib/src/app/routers/auth.js +29 -5
  87. package/lib/src/app/routers/control.js +20 -2
  88. package/lib/src/app/routers/dashboard.js +69 -26
  89. package/lib/src/app/routers/grid.js +37 -13
  90. package/lib/src/app/routers/profile.js +21 -0
  91. package/lib/src/app/routers/streamType.js +7 -2
  92. package/lib/src/app/routers/users.js +10 -5
  93. package/lib/src/app/routers/webhook.js +8 -4
  94. package/lib/src/app/swagger.js +2 -0
  95. package/lib/src/app/ws/h264StreamWs.js +10 -1
  96. package/lib/src/config.js +51 -5
  97. package/lib/src/dashboard/commands.js +185 -141
  98. package/lib/src/dashboard/event-manager.js +88 -67
  99. package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
  100. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  101. package/lib/src/data-service/appleFamily.js +43 -0
  102. package/lib/src/data-service/db.js +11 -0
  103. package/lib/src/data-service/device-service.js +6 -1
  104. package/lib/src/data-service/device-store.js +76 -3
  105. package/lib/src/data-service/deviceClaims.js +37 -0
  106. package/lib/src/data-service/deviceFieldOwners.js +25 -11
  107. package/lib/src/data-service/deviceSettings.js +79 -0
  108. package/lib/src/data-service/prisma-store.js +169 -15
  109. package/lib/src/data-service/web-config-service.js +50 -92
  110. package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
  111. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  112. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  113. package/lib/src/device-managers/ios/WDAClient.js +44 -43
  114. package/lib/src/device-managers/shellCommands.js +96 -0
  115. package/lib/src/device-utils.js +114 -40
  116. package/lib/src/gateway/defaultGateway.js +1 -1
  117. package/lib/src/gateway/forwardToNode.js +2 -0
  118. package/lib/src/gateway/healReport.js +87 -0
  119. package/lib/src/gateway/sessionGateway.js +12 -2
  120. package/lib/src/generated/client/edge.js +18 -3
  121. package/lib/src/generated/client/index-browser.js +15 -0
  122. package/lib/src/generated/client/index.d.ts +2086 -379
  123. package/lib/src/generated/client/index.js +18 -3
  124. package/lib/src/generated/client/package.json +1 -1
  125. package/lib/src/generated/client/schema.prisma +38 -7
  126. package/lib/src/generated/client/wasm.js +15 -0
  127. package/lib/src/interceptors/CommandInterceptor.js +271 -188
  128. package/lib/src/interceptors/xenonScripts.js +55 -0
  129. package/lib/src/interfaces/IPluginArgs.js +1 -2
  130. package/lib/src/middleware/authMiddleware.js +15 -3
  131. package/lib/src/middleware/commandCaller.js +3 -3
  132. package/lib/src/middleware/verifyCredential.js +58 -1
  133. package/lib/src/plugin.js +23 -4
  134. package/lib/src/prisma.js +1 -1
  135. package/lib/src/scripts/database-check.js +124 -0
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +281 -32
  138. package/lib/src/services/CleanupService.js +6 -1
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +70 -13
  141. package/lib/src/services/NetworkConditioningService.js +85 -88
  142. package/lib/src/services/NotificationService.js +36 -29
  143. package/lib/src/services/OrphanSweeper.js +7 -0
  144. package/lib/src/services/PortAllocator.js +5 -0
  145. package/lib/src/services/SelectorVerificationJob.js +15 -4
  146. package/lib/src/services/ServerManager.js +105 -9
  147. package/lib/src/services/SessionLifecycleService.js +133 -75
  148. package/lib/src/services/ShutdownCoordinator.js +33 -0
  149. package/lib/src/services/SocketServer.js +19 -3
  150. package/lib/src/services/TeamService.js +20 -3
  151. package/lib/src/services/bug-report/video-slice.js +4 -1
  152. package/lib/src/services/device-access/leaseOverride.js +7 -5
  153. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  154. package/lib/src/services/healing/HealEtalonService.js +3 -2
  155. package/lib/src/services/healing/HealingOrchestrator.js +85 -71
  156. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  157. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  158. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  159. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  160. package/lib/src/services/healing/resilioPath.js +274 -0
  161. package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
  162. package/lib/src/services/interceptor/interceptorOptions.js +44 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +163 -0
  165. package/lib/src/services/network/AndroidRadios.js +71 -0
  166. package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
  167. package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
  168. package/lib/src/services/network/adbForPhone.js +60 -0
  169. package/lib/src/services/network/phoneNetworkLock.js +20 -0
  170. package/lib/src/services/network/xenonProxy.js +107 -0
  171. package/lib/src/services/ocr/ocrData.js +118 -0
  172. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  173. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  174. package/lib/src/services/omni-vision/OmniVisionService.js +132 -95
  175. package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
  176. package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
  177. package/lib/src/services/omni-vision/screenScale.js +68 -0
  178. package/lib/src/services/passwordResetLink.js +50 -9
  179. package/lib/src/services/passwordResetMode.js +3 -1
  180. package/lib/src/services/publicUrl.js +45 -0
  181. package/lib/src/services/recording/concurrency-gate.js +12 -3
  182. package/lib/src/services/session/sessionIdentity.js +5 -3
  183. package/lib/src/services/session/shutdownReason.js +26 -0
  184. package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
  185. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  186. package/lib/src/services/settings/labSettings.js +149 -0
  187. package/lib/src/services/token/mintedLifetime.js +51 -0
  188. package/lib/src/services/webhookEvents.js +173 -0
  189. package/lib/src/sessions/SessionManager.js +8 -2
  190. package/lib/src/sessions/sessionMemory.js +25 -0
  191. package/lib/test/helpers/fake-adb.js +58 -0
  192. package/lib/test/helpers/fake-ai-provider.js +188 -0
  193. package/lib/test/helpers/fake-device-table.js +63 -24
  194. package/lib/test/helpers/loki-stores.js +33 -0
  195. package/lib/test/helpers/page-sources.js +164 -0
  196. package/lib/test/helpers/scratch-database.js +58 -13
  197. package/lib/test/integration/audit-ingest.spec.js +10 -10
  198. package/lib/test/integration/auth-flow.spec.js +9 -14
  199. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  200. package/lib/test/integration/bug-report-route.spec.js +22 -2
  201. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  202. package/lib/test/integration/forgot-password.spec.js +17 -18
  203. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  204. package/lib/test/integration/migration.spec.js +6 -1
  205. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  206. package/lib/test/integration/ports-router.spec.js +4 -4
  207. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  208. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  209. package/lib/test/integration/profile-tokens.spec.js +10 -16
  210. package/lib/test/integration/recordings-router.spec.js +16 -16
  211. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  212. package/lib/test/integration/role-matrix.spec.js +4 -9
  213. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  214. package/lib/test/integration/sdk-version.spec.js +2 -2
  215. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  216. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  217. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  218. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  219. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  220. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  221. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  222. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  223. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  224. package/lib/test/integration/users-flow.spec.js +7 -12
  225. package/lib/test/integration/users-lockout.spec.js +5 -3
  226. package/lib/test/unit/EmailService.test.js +66 -0
  227. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  228. package/lib/test/unit/TeamService.test.js +3 -0
  229. package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
  230. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  231. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  232. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  233. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  234. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  235. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  236. package/lib/test/unit/apikeys-router.spec.js +84 -0
  237. package/lib/test/unit/apple-family.spec.js +36 -0
  238. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  239. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  240. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  241. package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
  242. package/lib/test/unit/cleanup-settings.spec.js +244 -0
  243. package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
  244. package/lib/test/unit/dashboard-commands.spec.js +249 -0
  245. package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
  246. package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
  247. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  248. package/lib/test/unit/database-check.spec.js +108 -0
  249. package/lib/test/unit/default-plugin-args.spec.js +33 -0
  250. package/lib/test/unit/device-log-book.spec.js +180 -0
  251. package/lib/test/unit/device-offline-webhook.spec.js +81 -0
  252. package/lib/test/unit/device-setting-columns.spec.js +66 -0
  253. package/lib/test/unit/device-settings-loki.spec.js +87 -0
  254. package/lib/test/unit/device-settings-survive.spec.js +555 -0
  255. package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
  256. package/lib/test/unit/emulator-boot.spec.js +91 -0
  257. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  258. package/lib/test/unit/event-log-session-data.spec.js +244 -0
  259. package/lib/test/unit/failure-analysis.spec.js +182 -0
  260. package/lib/test/unit/failure-categories.spec.js +128 -0
  261. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  262. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  263. package/lib/test/unit/heal-report.spec.js +71 -0
  264. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  265. package/lib/test/unit/healing-tier-gate.spec.js +73 -29
  266. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  267. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +219 -0
  268. package/lib/test/unit/hub-node-same-host.spec.js +5 -0
  269. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  270. package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
  271. package/lib/test/unit/json-logging-option.spec.js +74 -0
  272. package/lib/test/unit/lab-settings.spec.js +218 -0
  273. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  274. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  275. package/lib/test/unit/max-sessions.spec.js +240 -0
  276. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  277. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  278. package/lib/test/unit/ocr-offline.spec.js +213 -0
  279. package/lib/test/unit/ocr-text-match.spec.js +44 -0
  280. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  281. package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
  282. package/lib/test/unit/omni-vision-ios-points.spec.js +159 -0
  283. package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
  284. package/lib/test/unit/omni-vision.spec.js +6 -8
  285. package/lib/test/unit/package-engines.spec.js +27 -0
  286. package/lib/test/unit/partial-config-file.spec.js +14 -0
  287. package/lib/test/unit/passwordResetMode.test.js +10 -2
  288. package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
  289. package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
  290. package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
  291. package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
  292. package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
  293. package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
  294. package/lib/test/unit/phone-network/session-start.spec.js +110 -0
  295. package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
  296. package/lib/test/unit/plugin.spec.js +3 -3
  297. package/lib/test/unit/profile-router.test.js +122 -1
  298. package/lib/test/unit/queue-requester.spec.js +1 -1
  299. package/lib/test/unit/recording-options.spec.js +181 -0
  300. package/lib/test/unit/reset-link-address.spec.js +266 -0
  301. package/lib/test/unit/reset-link.test.js +10 -4
  302. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  303. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  304. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  305. package/lib/test/unit/selector-verification-job.spec.js +13 -13
  306. package/lib/test/unit/self-healing-boot.spec.js +80 -0
  307. package/lib/test/unit/self-healing-setting.spec.js +273 -0
  308. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  309. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  310. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  311. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  312. package/lib/test/unit/session-device-logs.spec.js +347 -0
  313. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  314. package/lib/test/unit/session-failed-webhook.spec.js +170 -0
  315. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  316. package/lib/test/unit/shell-commands.spec.js +133 -0
  317. package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
  318. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  319. package/lib/test/unit/stream-start-pipelines.spec.js +264 -0
  320. package/lib/test/unit/stream-status-type.spec.js +4 -0
  321. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  322. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  323. package/lib/test/unit/verify-credential.spec.js +33 -0
  324. package/lib/test/unit/vision-assertion.spec.js +119 -0
  325. package/lib/test/unit/wda-client-shell.spec.js +133 -0
  326. package/lib/test/unit/web-config-service.spec.js +98 -0
  327. package/lib/test/unit/webhook-delivery.spec.js +24 -0
  328. package/lib/test/unit/webhook-payloads.spec.js +273 -0
  329. package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
  330. package/lib/tsconfig.tsbuildinfo +1 -1
  331. package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
  332. package/package.json +5 -5
  333. package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
  334. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  335. package/prisma/schema.prisma +38 -7
  336. package/schema.json +23 -25
  337. package/scripts/generate-types-from-schema.js +1 -2
  338. package/scripts/lib/always-present-plugin-args.js +4 -1
  339. package/lib/public/assets/BugReportButton-BoGhlI7F.js +0 -15
  340. package/lib/public/assets/DeviceMosaicView-8J0oYqMM.js +0 -2
  341. package/lib/public/assets/ai-settings-Bce5SJob.js +0 -21
  342. package/lib/public/assets/device-explorer-C-KnhMHI.js +0 -279
  343. package/lib/public/assets/forgot-password-DnudqFFo.js +0 -1
  344. package/lib/public/assets/maintenance-settings-zDVlOMVK.js +0 -6
  345. package/lib/public/assets/recording-group-store-WVM3hoXn.js +0 -26
  346. package/lib/public/assets/runbook-page-DJTVCJX7.js +0 -124
  347. package/lib/public/assets/session-detail-page-CGn55Pvl.js +0 -4
  348. package/lib/public/assets/settings-MsVU2ukE.js +0 -6
  349. package/lib/public/assets/webhook-settings-DiS8geIm.js +0 -1
  350. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = require("chai");
50
+ const sinon_1 = __importDefault(require("sinon"));
51
+ const typedi_1 = require("typedi");
52
+ const assetManager = __importStar(require("../../src/dashboard/asset-manager"));
53
+ const commands_1 = require("../../src/dashboard/commands");
54
+ const SessionManager_1 = require("../../src/sessions/SessionManager");
55
+ const SocketServer_1 = require("../../src/services/SocketServer");
56
+ const container_registration_1 = require("../helpers/container-registration");
57
+ const scratch_database_1 = require("../helpers/scratch-database");
58
+ /**
59
+ * The five execute commands that write to a session's record:
60
+ * setSessionName, setSessionStatus, debug, addTag and captureEvidence.
61
+ *
62
+ * A session has a record (a Session row) only on a hub with its dashboard
63
+ * on. Elsewhere (the dashboard off, or on a node, which never keeps one for
64
+ * the hub's sessions) setSessionName and setSessionStatus threw Prisma's
65
+ * P2025 and debug a foreign-key error, failing the test's command, while
66
+ * addTag and captureEvidence answered as if they had worked.
67
+ *
68
+ * Each now answers `{ recorded: true }` when it wrote, or `{ recorded: false,
69
+ * message }` saying why nothing was written. None of them fails the test's
70
+ * command: they are bookkeeping, and a test shouldn't die because its server
71
+ * keeps no dashboard.
72
+ */
73
+ describe('xenon: session-details commands', () => {
74
+ const scratch = (0, scratch_database_1.useScratchDatabase)();
75
+ const SESSION = 'session-details-1';
76
+ let restoreContainer;
77
+ let emitted;
78
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
79
+ yield scratch.db.log.deleteMany({});
80
+ yield scratch.db.sessionLog.deleteMany({});
81
+ yield scratch.db.session.deleteMany({});
82
+ restoreContainer = (0, container_registration_1.saveRegistrations)(SocketServer_1.SocketServer);
83
+ const socket = new SocketServer_1.SocketServer();
84
+ typedi_1.Container.set(SocketServer_1.SocketServer, socket);
85
+ emitted = sinon_1.default.stub(socket, 'emitToDashboardForDevices').resolves();
86
+ }));
87
+ afterEach(() => {
88
+ SessionManager_1.SESSION_MANAGER.removeSession(SESSION);
89
+ sinon_1.default.restore();
90
+ restoreContainer();
91
+ });
92
+ function withRecord() {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ yield scratch.db.session.create({
95
+ data: {
96
+ id: SESSION,
97
+ desired_capabilities: '{}',
98
+ session_capabilities: '{}',
99
+ node_id: 'node-1',
100
+ has_live_video: false,
101
+ device_udid: 'phone-1',
102
+ device_platform: 'android',
103
+ device_version: '14',
104
+ },
105
+ });
106
+ });
107
+ }
108
+ /** What the command answers, as the local interceptor and the hub's gateway both see it. */
109
+ function run(script, args) {
110
+ return __awaiter(this, void 0, void 0, function* () {
111
+ let body;
112
+ const response = {
113
+ status: (code) => ({
114
+ json: (d) => {
115
+ body = Object.assign({ code }, d);
116
+ return d;
117
+ },
118
+ }),
119
+ };
120
+ yield commands_1.dashboardCommands.process(SESSION, { body: { script, args } }, response);
121
+ return body;
122
+ });
123
+ }
124
+ describe('which scripts are session-details commands', () => {
125
+ it('knows the five, under either prefix, and nothing else', () => {
126
+ (0, chai_1.expect)((0, commands_1.sessionDetailsCommandOf)('xenon: setSessionName')).to.equal('setSessionName');
127
+ (0, chai_1.expect)((0, commands_1.sessionDetailsCommandOf)('xe:addTag')).to.equal('addTag');
128
+ (0, chai_1.expect)((0, commands_1.sessionDetailsCommandOf)(' xenon : captureEvidence ')).to.equal('captureEvidence');
129
+ for (const other of [
130
+ 'xenon: smartTap',
131
+ 'xe: setAutowaitProperties',
132
+ 'xenon: addMock',
133
+ 'xenon: unknownThing',
134
+ 'mobile: shell',
135
+ 'xenonsetSessionName',
136
+ 'return 1',
137
+ undefined,
138
+ 42,
139
+ ]) {
140
+ (0, chai_1.expect)((0, commands_1.sessionDetailsCommandOf)(other), String(other)).to.equal(null);
141
+ }
142
+ });
143
+ });
144
+ describe('with no session record', () => {
145
+ const cases = [
146
+ ['xenon: setSessionName', ['Checkout']],
147
+ ['xenon: setSessionStatus', [{ status: 'passed' }]],
148
+ ['xenon: debug', ['reached checkout']],
149
+ ['xenon: addTag', ['smoke']],
150
+ ['xenon: captureEvidence', ['Payment confirmed']],
151
+ ];
152
+ for (const [script, args] of cases) {
153
+ it(`${script} answers that nothing was recorded, and writes nothing`, () => __awaiter(void 0, void 0, void 0, function* () {
154
+ const body = yield run(script, args);
155
+ (0, chai_1.expect)(body.code).to.equal(200);
156
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
157
+ (0, chai_1.expect)(body.value.message).to.match(/no record/i);
158
+ (0, chai_1.expect)(yield scratch.db.log.count()).to.equal(0);
159
+ (0, chai_1.expect)(yield scratch.db.sessionLog.count()).to.equal(0);
160
+ }));
161
+ }
162
+ });
163
+ describe('with a session record', () => {
164
+ beforeEach(withRecord);
165
+ it('setSessionName names the session', () => __awaiter(void 0, void 0, void 0, function* () {
166
+ var _a, _b;
167
+ (0, chai_1.expect)((yield run('xenon: setSessionName', ['Checkout'])).value).to.deep.equal({
168
+ recorded: true,
169
+ });
170
+ (0, chai_1.expect)((_a = (yield scratch.db.session.findUnique({ where: { id: SESSION } }))) === null || _a === void 0 ? void 0 : _a.name).to.equal('Checkout');
171
+ yield run('xe: setSessionName', [{ name: 'Checkout by card' }]);
172
+ (0, chai_1.expect)((_b = (yield scratch.db.session.findUnique({ where: { id: SESSION } }))) === null || _b === void 0 ? void 0 : _b.name).to.equal('Checkout by card');
173
+ }));
174
+ it('setSessionName without a name records nothing, and says so', () => __awaiter(void 0, void 0, void 0, function* () {
175
+ const body = yield run('xenon: setSessionName', []);
176
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
177
+ (0, chai_1.expect)(body.value.message).to.match(/name/);
178
+ }));
179
+ it('setSessionStatus sets the status and reason, and tells the dashboard', () => __awaiter(void 0, void 0, void 0, function* () {
180
+ var _a;
181
+ const body = yield run('xenon: setSessionStatus', [{ status: 'failed', reason: 'No total' }]);
182
+ (0, chai_1.expect)(body.value).to.deep.equal({ recorded: true });
183
+ const row = yield scratch.db.session.findUnique({ where: { id: SESSION } });
184
+ (0, chai_1.expect)(row).to.include({ status: 'failed', failure_reason: 'No total' });
185
+ (0, chai_1.expect)(emitted.firstCall.args[1]).to.include({
186
+ id: SESSION,
187
+ status: 'failed',
188
+ failure_reason: 'No total',
189
+ });
190
+ yield run('xenon: setSessionStatus', ['passed']);
191
+ (0, chai_1.expect)((_a = (yield scratch.db.session.findUnique({ where: { id: SESSION } }))) === null || _a === void 0 ? void 0 : _a.status).to.equal('success');
192
+ }));
193
+ it('setSessionStatus with a status it does not know records nothing, and says so', () => __awaiter(void 0, void 0, void 0, function* () {
194
+ var _a;
195
+ const body = yield run('xenon: setSessionStatus', [{ status: 'skipped' }]);
196
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
197
+ (0, chai_1.expect)(body.value.message).to.match(/skipped/);
198
+ (0, chai_1.expect)((_a = (yield scratch.db.session.findUnique({ where: { id: SESSION } }))) === null || _a === void 0 ? void 0 : _a.status).to.equal('running');
199
+ }));
200
+ it('debug adds a debug line', () => __awaiter(void 0, void 0, void 0, function* () {
201
+ (0, chai_1.expect)((yield run('xenon: debug', ['reached checkout'])).value).to.deep.equal({
202
+ recorded: true,
203
+ });
204
+ yield run('xenon: debug', [{ message: 'paid' }]);
205
+ const lines = yield scratch.db.log.findMany({ orderBy: { createdAt: 'asc' } });
206
+ (0, chai_1.expect)(lines.map((l) => [l.log_type, l.message])).to.deep.equal([
207
+ ['DEBUG', 'reached checkout'],
208
+ ['DEBUG', 'paid'],
209
+ ]);
210
+ }));
211
+ it('debug without a message records nothing, and says so', () => __awaiter(void 0, void 0, void 0, function* () {
212
+ const body = yield run('xenon: debug', []);
213
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
214
+ (0, chai_1.expect)(yield scratch.db.log.count()).to.equal(0);
215
+ }));
216
+ it('addTag adds a tag once', () => __awaiter(void 0, void 0, void 0, function* () {
217
+ (0, chai_1.expect)((yield run('xenon: addTag', ['smoke'])).value).to.deep.equal({ recorded: true });
218
+ (0, chai_1.expect)((yield run('xenon: addTag', [{ tag: 'smoke' }])).value).to.deep.equal({
219
+ recorded: true,
220
+ });
221
+ yield run('xenon: addTag', ['regression']);
222
+ const row = yield scratch.db.session.findUnique({ where: { id: SESSION } });
223
+ (0, chai_1.expect)(JSON.parse(String(row === null || row === void 0 ? void 0 : row.tags))).to.deep.equal(['smoke', 'regression']);
224
+ }));
225
+ it('captureEvidence records a screenshot of a session this server drives', () => __awaiter(void 0, void 0, void 0, function* () {
226
+ SessionManager_1.SESSION_MANAGER.addSession(SESSION, { getScreenShot: () => __awaiter(void 0, void 0, void 0, function* () { return 'aGVsbG8='; }) });
227
+ sinon_1.default.stub(assetManager, 'saveScreenShot').returns(`${SESSION}/screenshots/x.png`);
228
+ const body = yield run('xenon: captureEvidence', [{ reason: 'Paid', label: 'receipt' }]);
229
+ (0, chai_1.expect)(body.value).to.deep.equal({ recorded: true });
230
+ const rows = yield scratch.db.sessionLog.findMany({});
231
+ (0, chai_1.expect)(rows.map((r) => [r.command_name, r.subtitle, r.screenshot])).to.deep.equal([
232
+ ['captureEvidence', 'Paid', `${SESSION}/screenshots/x.png`],
233
+ ]);
234
+ }));
235
+ it("captureEvidence records nothing, and says so, when it can't take a screenshot", () => __awaiter(void 0, void 0, void 0, function* () {
236
+ let body = yield run('xenon: captureEvidence', ['Paid']);
237
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
238
+ SessionManager_1.SESSION_MANAGER.addSession(SESSION, {
239
+ getScreenShot: () => __awaiter(void 0, void 0, void 0, function* () {
240
+ throw new Error('WDA is not running');
241
+ }),
242
+ });
243
+ body = yield run('xenon: captureEvidence', ['Paid']);
244
+ (0, chai_1.expect)(body.value).to.include({ recorded: false });
245
+ (0, chai_1.expect)(body.value.message).to.match(/WDA is not running/);
246
+ (0, chai_1.expect)(yield scratch.db.sessionLog.count()).to.equal(0);
247
+ }));
248
+ });
249
+ });
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = require("chai");
50
+ const sinon_1 = __importDefault(require("sinon"));
51
+ const typedi_1 = require("typedi");
52
+ const event_manager_1 = require("../../src/dashboard/event-manager");
53
+ const assets = __importStar(require("../../src/dashboard/asset-manager"));
54
+ const sessionService = __importStar(require("../../src/dashboard/services/session-service"));
55
+ const SessionManager_1 = require("../../src/sessions/SessionManager");
56
+ const SessionMetricsService_1 = require("../../src/services/metrics/SessionMetricsService");
57
+ const SessionDeviceLogs_1 = require("../../src/services/logcat/SessionDeviceLogs");
58
+ const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
59
+ const SocketServer_1 = require("../../src/services/SocketServer");
60
+ const TracingService_1 = require("../../src/services/TracingService");
61
+ const MetricsService_1 = require("../../src/services/MetricsService");
62
+ const device_store_1 = require("../../src/data-service/device-store");
63
+ const PluginContext_1 = require("../../src/PluginContext");
64
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
65
+ const pendingSessions = __importStar(require("../../src/data-service/pending-sessions-service"));
66
+ const deviceService = __importStar(require("../../src/data-service/device-service"));
67
+ const deviceUtils = __importStar(require("../../src/device-utils"));
68
+ const config_1 = require("../../src/config");
69
+ const prisma_1 = require("../../src/prisma");
70
+ const container_registration_1 = require("../helpers/container-registration");
71
+ const loki_stores_1 = require("../helpers/loki-stores");
72
+ /**
73
+ * A session's device log is recorded from about when its phone was given to
74
+ * it, once the session's row exists (the lines point at it), and written to
75
+ * the end however the session ends: every ending reaches onSessionStopped.
76
+ */
77
+ describe('EventManager: an Android session’s device log', () => {
78
+ let restore;
79
+ let deviceLogs;
80
+ let order;
81
+ beforeEach(() => {
82
+ restore = (0, container_registration_1.saveRegistrations)(SessionDeviceLogs_1.SessionDeviceLogs, SessionMetricsService_1.SessionMetricsService, SocketServer_1.SocketServer);
83
+ order = [];
84
+ deviceLogs = {
85
+ start: sinon_1.default.stub().callsFake(() => __awaiter(void 0, void 0, void 0, function* () { return order.push('device logs'); })),
86
+ stop: sinon_1.default.stub().resolves(),
87
+ };
88
+ typedi_1.Container.set(SessionDeviceLogs_1.SessionDeviceLogs, deviceLogs);
89
+ typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, {
90
+ appliesTo: () => false,
91
+ start: sinon_1.default.stub(),
92
+ stop: sinon_1.default.stub().resolves(),
93
+ });
94
+ typedi_1.Container.set(SocketServer_1.SocketServer, {
95
+ emitToDashboard: sinon_1.default.stub(),
96
+ emitToDashboardForDevices: sinon_1.default.stub().resolves(),
97
+ hasScopedDashboard: sinon_1.default.stub().returns(false),
98
+ });
99
+ });
100
+ afterEach(() => {
101
+ sinon_1.default.restore();
102
+ restore();
103
+ });
104
+ it("starts once the session's row is written, from when the phone was given to it", () => __awaiter(void 0, void 0, void 0, function* () {
105
+ sinon_1.default.stub(assets, 'prepareDirectory');
106
+ sinon_1.default.stub(sessionService, 'getOrCreateNewBuild').resolves({ id: 'b-logs' });
107
+ sinon_1.default.stub(TracingService_1.TracingService.prototype, 'getTraceId').returns('t-1');
108
+ sinon_1.default.stub(MetricsService_1.MetricsService.prototype, 'incrementSessionStart');
109
+ sinon_1.default.stub(prisma_1.prisma.session, 'create').callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
110
+ order.push('row');
111
+ return {};
112
+ }));
113
+ const session = {
114
+ getId: () => 's-logs-1',
115
+ getCapabilities: () => ({ platformName: 'Android' }),
116
+ getLiveVideoUrl: () => null,
117
+ apiKeyId: null,
118
+ userId: null,
119
+ allocatedAt: 1700000000000,
120
+ };
121
+ const device = { udid: 'phone-l', platform: 'android', host: 'h', name: 'S9', sdk: '10' };
122
+ yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted({}, session, device);
123
+ (0, chai_1.expect)(order).to.deep.equal(['row', 'device logs']);
124
+ (0, chai_1.expect)(deviceLogs.start.firstCall.args[0]).to.deep.equal({
125
+ sessionId: 's-logs-1',
126
+ device,
127
+ since: 1700000000000,
128
+ });
129
+ }));
130
+ it('writes it to the end when the session stops, even one no longer in memory', () => __awaiter(void 0, void 0, void 0, function* () {
131
+ sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'getSession').returns(undefined);
132
+ sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({ getDevices: () => __awaiter(void 0, void 0, void 0, function* () { return []; }) });
133
+ sinon_1.default
134
+ .stub(prisma_1.prisma.session, 'findFirst')
135
+ .resolves({ id: 's-logs-2', status: 'running', device_udid: 'phone-l' });
136
+ sinon_1.default.stub(prisma_1.prisma.session, 'update').resolves({});
137
+ sinon_1.default.stub(prisma_1.prisma.sessionLog, 'findFirst').resolves(null);
138
+ let statusWritten = false;
139
+ prisma_1.prisma.session.update.callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
140
+ statusWritten = true;
141
+ return {};
142
+ }));
143
+ deviceLogs.stop.callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
144
+ // The session page reads the device log again once the session has
145
+ // ended: by then every line is written.
146
+ (0, chai_1.expect)(statusWritten, 'the session was marked ended first').to.equal(false);
147
+ }));
148
+ yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStopped('s-logs-2');
149
+ (0, chai_1.expect)(deviceLogs.stop.calledOnceWithExactly('s-logs-2')).to.equal(true);
150
+ (0, chai_1.expect)(statusWritten).to.equal(true);
151
+ }));
152
+ });
153
+ describe('createSession: when the phone was given to the session', () => {
154
+ (0, loki_stores_1.useLokiStores)();
155
+ let context;
156
+ let saved;
157
+ let authDisabledBefore;
158
+ beforeEach(() => {
159
+ authDisabledBefore = config_1.config.authDisabled;
160
+ config_1.config.authDisabled = true;
161
+ context = typedi_1.Container.get(PluginContext_1.PluginContext);
162
+ saved = { pluginArgs: context.pluginArgs, nodeId: context.nodeId };
163
+ context.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: '127.0.0.1' });
164
+ context.nodeId = 'node-here';
165
+ });
166
+ afterEach(() => {
167
+ config_1.config.authDisabled = authDisabledBefore;
168
+ Object.assign(context, saved);
169
+ sinon_1.default.restore();
170
+ });
171
+ it('is noted on the session, between the allocation and the driver starting', () => __awaiter(void 0, void 0, void 0, function* () {
172
+ const device = {
173
+ udid: 'u1',
174
+ host: 'h1',
175
+ platform: 'android',
176
+ nodeId: 'node-here',
177
+ teamId: null,
178
+ };
179
+ let allocatedBy = 0;
180
+ let driverAt = 0;
181
+ sinon_1.default.stub(pendingSessions, 'addNewPendingSession').resolves();
182
+ sinon_1.default.stub(pendingSessions, 'removePendingSession').resolves();
183
+ sinon_1.default.stub(deviceService, 'updateDeviceProgress').resolves();
184
+ sinon_1.default.stub(deviceService, 'updatedAllocatedDevice').resolves();
185
+ sinon_1.default.stub(deviceUtils, 'allocateDeviceForSession').callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
186
+ allocatedBy = Date.now();
187
+ return device;
188
+ }));
189
+ const svc = new SessionLifecycleService_1.SessionLifecycleService();
190
+ const instance = {};
191
+ sinon_1.default.stub(svc, 'createSessionInstance').returns(instance);
192
+ sinon_1.default.stub(svc, 'applyPostSessionLogic').resolves();
193
+ const next = sinon_1.default.stub().callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
194
+ driverAt = Date.now();
195
+ // A slow driver start: the session's log still counts from before it.
196
+ yield new Promise((r) => setTimeout(r, 20));
197
+ return { value: ['s-alloc', { platformName: 'Android' }] };
198
+ }));
199
+ yield svc.createSession(next, {}, {
200
+ alwaysMatch: { platformName: 'Android', 'appium:automationName': 'UiAutomator2' },
201
+ firstMatch: [{}],
202
+ });
203
+ (0, chai_1.expect)(instance.allocatedAt).to.be.a('number');
204
+ (0, chai_1.expect)(instance.allocatedAt).to.be.at.least(allocatedBy);
205
+ (0, chai_1.expect)(instance.allocatedAt).to.be.at.most(driverAt);
206
+ }));
207
+ });
@@ -253,6 +253,8 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
253
253
  delete teams[filter.udid];
254
254
  })),
255
255
  updateDevice: sinon_1.default.stub().resolves(),
256
+ // removeDevice reads the row for the device_offline webhook's name and platform.
257
+ findDevice: sinon_1.default.stub().resolves(null),
256
258
  getDevices: sinon_1.default.stub().resolves([
257
259
  {
258
260
  udid: 'phone-b',
@@ -308,13 +310,14 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
308
310
  }));
309
311
  });
310
312
  it("xenon: setSessionStatus: the updated session's device", () => __awaiter(void 0, void 0, void 0, function* () {
313
+ sinon_1.default.stub(prisma_1.prisma.session, 'findUnique').resolves({ id: 's3', tags: null });
311
314
  sinon_1.default.stub(prisma_1.prisma.session, 'update').resolves({ id: 's3', device_udid: 'phone-b' });
312
315
  const res = { status: () => res, json: () => res };
313
316
  yield new commands_1.DashboardCommands().process('s3', {
314
317
  body: { script: 'xenon: setSessionStatus', args: [{ status: 'failed', reason: 'boom' }] },
315
318
  }, res);
316
319
  const [call] = scoped(SocketEvents_1.SocketEvents.SESSION_STOPPED);
317
- (0, chai_1.expect)(call.args[1]).to.deep.equal({ id: 's3', status: 'failed', failure_reason: undefined });
320
+ (0, chai_1.expect)(call.args[1]).to.deep.equal({ id: 's3', status: 'failed', failure_reason: 'boom' });
318
321
  (0, chai_1.expect)(call.args[2]).to.deep.equal({ udid: 'phone-b' });
319
322
  }));
320
323
  describe('interceptor', () => {
@@ -400,11 +403,10 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
400
403
  }));
401
404
  it('a team change (PUT /device/:udid/team) refreshes the resolver at once', () => __awaiter(void 0, void 0, void 0, function* () {
402
405
  sinon_1.default.stub(prisma_1.prisma.team, 'findUnique').resolves({ id: 'team-c' });
403
- sinon_1.default.stub(prisma_1.prisma.device, 'updateMany').resolves({ count: 1 });
404
- sinon_1.default
405
- .stub(prisma_1.prisma.device, 'findMany')
406
- .resolves([{ udid: 'phone-a', host: 'h', teamId: 'team-c' }]);
407
- sinon_1.default.stub(device_store_1.DeviceStoreFactory.getStore(), 'updateDevice').resolves();
406
+ const store = device_store_1.DeviceStoreFactory.getStore();
407
+ sinon_1.default.stub(store, 'findDevices').resolves([{ udid: 'phone-a', host: 'h' }]);
408
+ sinon_1.default.stub(store, 'findSavedPhones').resolves([]);
409
+ const write = sinon_1.default.stub(store, 'updateDevice').resolves();
408
410
  const resolver = typedi_1.Container.get(DeviceTeamResolver_1.DeviceTeamResolver);
409
411
  yield resolver.resolve('phone-a'); // cached as team-a
410
412
  const app = (0, express_1.default)();
@@ -424,6 +426,7 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
424
426
  app.use(router);
425
427
  const res = yield (0, loopbackRequest_1.default)(app).put('/device/phone-a/team').send({ teamId: 'team-c' });
426
428
  (0, chai_1.expect)(res.status).to.equal(200);
429
+ (0, chai_1.expect)(write.firstCall.args).to.deep.equal(['phone-a', 'h', { teamId: 'team-c' }]);
427
430
  (0, chai_1.expect)(yield resolver.resolve('phone-a')).to.deep.equal({ known: true, teamId: 'team-c' });
428
431
  (0, chai_1.expect)(lookups).to.deep.equal(['phone-a']);
429
432
  }));