@xenon-device-management/xenon 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -45,60 +45,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.analyzeSessionFailure = analyzeSessionFailure;
48
+ exports.MAX_CONCURRENT_FAILURE_ANALYSES = void 0;
49
+ exports.categorizeSessionFailure = categorizeSessionFailure;
50
+ exports.explainSessionFailure = explainSessionFailure;
49
51
  const prisma_1 = require("../../prisma");
50
52
  const logger_1 = __importDefault(require("../../logger"));
51
- const ERROR_PATTERNS = [
52
- {
53
- category: 'ELEMENT_NOT_FOUND',
54
- patterns: [
55
- 'NoSuchElementError',
56
- 'unable to find an element',
57
- 'An element could not be located',
58
- 'no such element',
59
- ],
60
- },
61
- {
62
- category: 'APP_CRASH',
63
- patterns: [
64
- 'Appium crashed',
65
- 'process has died',
66
- 'activity has died',
67
- 'The application has crashed',
68
- 'Application not responding',
69
- "org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. Original error: The application under test with bundle id '.*' is not running or cannot be found",
70
- ],
71
- },
72
- {
73
- category: 'TIMEOUT',
74
- patterns: ['timeout', 'timed out', 'TimeoutException', 'New Command Timeout', 'socket hang up'],
75
- },
76
- {
77
- category: 'PERMISSION_BLOCKED',
78
- patterns: ['Permission alert', 'Security alert', 'Always Allow', 'Allow while using app'],
79
- },
80
- {
81
- category: 'WDA_FAILURE',
82
- patterns: [
83
- 'WebDriverAgent',
84
- 'WDA',
85
- 'xcodebuild failed',
86
- 'crashed with code',
87
- 'Unable to connect to WDA',
88
- 'Session does not exist',
89
- 'the session is not in a running state',
90
- ],
91
- },
92
- {
93
- category: 'XENON_COMMAND_FAILURE',
94
- patterns: ['Command failed', 'telemetry failed', 'interceptor error'],
95
- },
96
- {
97
- category: 'SYSTEM_OVERLOAD',
98
- patterns: ['OutOfMemory', 'MemoryLimit', 'thermal throttling', 'too many open files'],
99
- },
100
- ];
101
- function analyzeSessionFailure(sessionId) {
53
+ const failureCategories_1 = require("./failureCategories");
54
+ /**
55
+ * How many failed sessions' AI analyses run at once (explainSessionFailure);
56
+ * the others wait their turn. They run after their sessions have ended, so
57
+ * nothing else limits them: a broken build on a big lab ends hundreds of
58
+ * failed sessions in minutes, and each analysis holds a screenshot while it
59
+ * runs. Few at once also spares the provider's rate limit.
60
+ */
61
+ exports.MAX_CONCURRENT_FAILURE_ANALYSES = 4;
62
+ /** Each session's analysis, waiting or running, so one session gets one. */
63
+ const analyses = new Map();
64
+ let analysesRunning = 0;
65
+ const analysesWaiting = [];
66
+ function analysisTurn() {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ if (analysesRunning < exports.MAX_CONCURRENT_FAILURE_ANALYSES) {
69
+ analysesRunning++;
70
+ return;
71
+ }
72
+ // The turn is handed over by endAnalysisTurn, still counted as running.
73
+ yield new Promise((resolve) => analysesWaiting.push(resolve));
74
+ });
75
+ }
76
+ function endAnalysisTurn() {
77
+ const next = analysesWaiting.shift();
78
+ if (next)
79
+ next();
80
+ else
81
+ analysesRunning--;
82
+ }
83
+ /**
84
+ * Files a failed session under a category (`failure_category`) by its failure
85
+ * reason and its last five failed commands' errors (categorizeFailure, in
86
+ * failureCategories.ts). Rules only, so it is quick: a session's end waits for
87
+ * it. Never throws.
88
+ */
89
+ function categorizeSessionFailure(sessionId) {
102
90
  return __awaiter(this, void 0, void 0, function* () {
103
91
  try {
104
92
  const session = yield prisma_1.prisma.session.findUnique({
@@ -109,61 +97,11 @@ function analyzeSessionFailure(sessionId) {
109
97
  });
110
98
  if (!session)
111
99
  return;
112
- const reason = session.failure_reason || '';
113
- const logs_text = session.SessionLog.map((l) => `${l.title} ${l.response}`).join(' ');
114
- const combined_text = (reason + ' ' + logs_text).toLowerCase();
115
- let identifiedCategory = 'UNKNOWN';
116
- for (const item of ERROR_PATTERNS) {
117
- if (item.patterns.some((p) => new RegExp(p, 'i').test(combined_text))) {
118
- identifiedCategory = item.category;
119
- break;
120
- }
121
- }
122
- // Special case for App Crash - check Logcat/Syslog if available
123
- // For now we rely on the error response text which usually mentions "process has died"
124
- logger_1.default.info(`[FailureAnalysis] Session ${sessionId} identified as ${identifiedCategory}`);
125
- // AI Root-Cause Analysis
126
- let aiAnalysis = null;
127
- try {
128
- const { AI_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../services/AIService')));
129
- if (AI_SERVICE.isEnabled()) {
130
- const lastLogs = yield prisma_1.prisma.log.findMany({
131
- where: { session_id: sessionId, log_type: 'DEVICE' },
132
- take: 50,
133
- orderBy: { timestamp: 'desc' },
134
- });
135
- const lastCommands = yield prisma_1.prisma.sessionLog.findMany({
136
- where: { session_id: sessionId },
137
- take: 10,
138
- orderBy: { createdAt: 'desc' },
139
- });
140
- // Find the last screenshot in logs
141
- const lastScreenshotLog = lastCommands.find((l) => l.screenshot !== null);
142
- aiAnalysis = yield AI_SERVICE.analyzeFailure({
143
- sessionId,
144
- failureReason: reason,
145
- commandLogs: lastCommands.map((c) => {
146
- var _a;
147
- return ({
148
- command: c.command_name,
149
- success: c.is_success,
150
- response: (_a = c.response) === null || _a === void 0 ? void 0 : _a.slice(0, 500), // Truncate long responses
151
- });
152
- }),
153
- deviceLogs: lastLogs.map((l) => l.message),
154
- screenshotPath: (lastScreenshotLog === null || lastScreenshotLog === void 0 ? void 0 : lastScreenshotLog.screenshot) || undefined,
155
- });
156
- }
157
- }
158
- catch (aiErr) {
159
- logger_1.default.warn(`[FailureAnalysis] AI Analysis failed for ${sessionId}: ${aiErr.message}`);
160
- }
100
+ const category = (0, failureCategories_1.categorizeFailure)(session.failure_reason || '', session.SessionLog.map((l) => (0, failureCategories_1.commandErrorOf)(l.response)));
101
+ logger_1.default.info(`[FailureAnalysis] Session ${sessionId} identified as ${category}`);
161
102
  yield prisma_1.prisma.session.update({
162
103
  where: { id: sessionId },
163
- data: {
164
- failure_category: identifiedCategory,
165
- ai_analysis: aiAnalysis,
166
- },
104
+ data: { failure_category: category },
167
105
  });
168
106
  }
169
107
  catch (err) {
@@ -171,3 +109,78 @@ function analyzeSessionFailure(sessionId) {
171
109
  }
172
110
  });
173
111
  }
112
+ /**
113
+ * Asks the AI provider why a failed session failed, and saves the answer
114
+ * (`ai_analysis`). Only an answer is saved: with no provider, a rate limit, a
115
+ * time-out (FAILURE_ANALYSIS_TIMEOUT_MS) or a failed call nothing is written,
116
+ * and an analysis saved earlier stays. Never rejects.
117
+ *
118
+ * A session's end doesn't wait for it (onSessionStopped): an AI call can take
119
+ * minutes, and the client's quit, or a hub's DELETE, waits for the end. At
120
+ * most MAX_CONCURRENT_FAILURE_ANALYSES run at once, and a session whose
121
+ * analysis is waiting or running gets that one (a session can end twice: a
122
+ * crash, then the client's delete).
123
+ */
124
+ function explainSessionFailure(sessionId) {
125
+ const pending = analyses.get(sessionId);
126
+ if (pending)
127
+ return pending;
128
+ const analysis = (() => __awaiter(this, void 0, void 0, function* () {
129
+ yield analysisTurn();
130
+ try {
131
+ yield explain(sessionId);
132
+ }
133
+ finally {
134
+ endAnalysisTurn();
135
+ }
136
+ }))().finally(() => analyses.delete(sessionId));
137
+ analyses.set(sessionId, analysis);
138
+ return analysis;
139
+ }
140
+ function explain(sessionId) {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ try {
143
+ const { AI_SERVICE } = yield Promise.resolve().then(() => __importStar(require('../../services/AIService')));
144
+ if (!AI_SERVICE.isEnabled())
145
+ return;
146
+ const session = yield prisma_1.prisma.session.findUnique({ where: { id: sessionId } });
147
+ if (!session)
148
+ return;
149
+ const lastLogs = yield prisma_1.prisma.log.findMany({
150
+ where: { session_id: sessionId, log_type: 'DEVICE' },
151
+ take: 50,
152
+ orderBy: { timestamp: 'desc' },
153
+ });
154
+ const lastCommands = yield prisma_1.prisma.sessionLog.findMany({
155
+ where: { session_id: sessionId },
156
+ take: 10,
157
+ orderBy: { createdAt: 'desc' },
158
+ });
159
+ // Find the last screenshot in logs
160
+ const lastScreenshotLog = lastCommands.find((l) => l.screenshot !== null);
161
+ const aiAnalysis = yield AI_SERVICE.analyzeFailure({
162
+ sessionId,
163
+ failureReason: session.failure_reason || '',
164
+ commandLogs: lastCommands.map((c) => {
165
+ var _a;
166
+ return ({
167
+ command: c.command_name,
168
+ success: c.is_success,
169
+ response: (_a = c.response) === null || _a === void 0 ? void 0 : _a.slice(0, 500), // Truncate long responses
170
+ });
171
+ }),
172
+ deviceLogs: lastLogs.map((l) => l.message),
173
+ screenshotPath: (lastScreenshotLog === null || lastScreenshotLog === void 0 ? void 0 : lastScreenshotLog.screenshot) || undefined,
174
+ });
175
+ if (!aiAnalysis)
176
+ return;
177
+ yield prisma_1.prisma.session.update({
178
+ where: { id: sessionId },
179
+ data: { ai_analysis: aiAnalysis },
180
+ });
181
+ }
182
+ catch (aiErr) {
183
+ logger_1.default.warn(`[FailureAnalysis] AI Analysis failed for ${sessionId}: ${aiErr.message}`);
184
+ }
185
+ });
186
+ }
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * What files a failed session under a `failure_category`, and how.
4
+ *
5
+ * The texts here are those the installed drivers, Appium and Xenon send
6
+ * (UiAutomator2 6.4.1 with its server 9.3.1, XCUITest 10.9.0 with
7
+ * WebDriverAgent 10.2.3, base-driver 10.x), plus a few a test may report in
8
+ * its own words (`xenon: setSessionStatus`), marked as such.
9
+ *
10
+ * Through 2.15 the rules were guesses:
11
+ * - none of App crash's texts existed anywhere;
12
+ * - a raw body's stack trace was matched too. Every UiAutomator2 error a hub
13
+ * records carries netty's `IdleStateHandler`, so one that matched no
14
+ * earlier rule was filed TIMEOUT;
15
+ * - the five failed commands were read as one text, so a failure the test
16
+ * had recovered from could decide the category.
17
+ *
18
+ * A rule matches a W3C error code exactly (`codes`), or a phrase anywhere in
19
+ * the reason or a command's error or message (`phrases`), ignoring case.
20
+ * Codes are only on the rows a hub records for a node's sessions: this
21
+ * server's own rows hold the message alone, so every category needs phrases
22
+ * too. Xenon's own messages are read first, by how they start, since they
23
+ * quote the test's selector (xenonOwnCategory). Rules are tried in this order.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.RETIRED_CATEGORIES = exports.ANALYSIS_CATEGORIES = exports.FAILURE_RULES = exports.UNMATCHED_CATEGORY = exports.HUB_RESTART_CATEGORY = void 0;
27
+ exports.xenonOwnCategory = xenonOwnCategory;
28
+ exports.commandErrorOf = commandErrorOf;
29
+ exports.categorizeFailure = categorizeFailure;
30
+ /** A session the server's restart or shutdown ended. SessionManager also writes it at boot. */
31
+ exports.HUB_RESTART_CATEGORY = 'HUB_RESTART';
32
+ /** What a failure that matches no rule is filed as. */
33
+ exports.UNMATCHED_CATEGORY = 'UNKNOWN';
34
+ exports.FAILURE_RULES = [
35
+ {
36
+ // ShutdownCoordinator's drain: the whole reason, never a phrase in one.
37
+ category: exports.HUB_RESTART_CATEGORY,
38
+ codes: ['Hub shutdown'],
39
+ phrases: [],
40
+ },
41
+ {
42
+ // Before SESSION_LOST: a proxied connect that fails with EMFILE ("Could
43
+ // not proxy command ... connect EMFILE") is the server out of files, not
44
+ // the helper gone. Not a bare "EMFILE": that is a substring of a locator
45
+ // such as "systemFileList".
46
+ category: 'SYSTEM_OVERLOAD',
47
+ codes: [],
48
+ phrases: ['OutOfMemory', 'too many open files', 'connect EMFILE'],
49
+ },
50
+ {
51
+ // The phone's automation helper (WebDriverAgent, the UiAutomator2
52
+ // server, Chromedriver) or the session itself went away during the run.
53
+ category: 'SESSION_LOST',
54
+ codes: ['invalid session id'],
55
+ phrases: [
56
+ // base-driver's proxy, when the helper doesn't answer at all (socket
57
+ // hang up, ECONNREFUSED, a read time-out).
58
+ 'Could not proxy command to the remote server',
59
+ // UiAutomator2: its server's instrumentation exited ("probably crashed"
60
+ // is the helper, not the app), or the phone went away.
61
+ 'instrumentation process is not running',
62
+ // WebDriverAgent restarted and lost the session.
63
+ 'Session does not exist',
64
+ 'A session is either terminated or not started',
65
+ // Appium's own, when a command races the session's end.
66
+ 'The session with id',
67
+ // Appium ended the session itself (onUnexpectedShutdown).
68
+ 'unexpectedly shut down',
69
+ 'Driver shut down unexpectedly',
70
+ 'Chromedriver quit unexpectedly',
71
+ // XCUITest's commandTimeouts, which then ends the session.
72
+ 'Appium did not get any response from',
73
+ // Xenon lost the session: OrphanSweeper, SessionHeartbeatService, and
74
+ // the hub's answer for a node it can't reach (forwardToNode).
75
+ 'Session heartbeat timeout',
76
+ 'Session terminal failure',
77
+ 'The node no longer has this session',
78
+ 'Node session status failed',
79
+ 'could not reach the node running this session',
80
+ ],
81
+ },
82
+ {
83
+ category: 'APP_CRASH',
84
+ codes: [],
85
+ phrases: [
86
+ // WebDriverAgent, when the app under test is gone.
87
+ 'is not running, possibly crashed',
88
+ // A test's own words.
89
+ 'The application has crashed',
90
+ 'Application not responding',
91
+ 'process has died',
92
+ 'activity has died',
93
+ ],
94
+ },
95
+ {
96
+ category: 'STALE_ELEMENT',
97
+ codes: ['stale element reference'],
98
+ phrases: [
99
+ // base-driver's and WebDriverAgent's default, and the UiAutomator2
100
+ // server's.
101
+ 'no longer attached to the DOM',
102
+ // The UiAutomator2 server's cache. "not present in the cache or has
103
+ // expired" is answered as "no such element", hence this rule first.
104
+ 'does not exist in DOM anymore',
105
+ 'do not exist in DOM anymore',
106
+ 'is not linked to the same object in DOM anymore',
107
+ 'is not present in the cache or has expired',
108
+ 'StaleObjectException',
109
+ // WebDriverAgent's cache and snapshots.
110
+ 'expired from the internal cache',
111
+ 'is not present in the current view anymore',
112
+ // Safari and web views on an iPhone.
113
+ 'Element does not exist in cache',
114
+ ],
115
+ },
116
+ {
117
+ // Specific phrases only: a bare "timeout" also matched Xenon's own
118
+ // autowait misses, a hung helper, heartbeat reasons and command titles.
119
+ category: 'TIMEOUT',
120
+ codes: ['timeout', 'script timeout'],
121
+ phrases: [
122
+ 'New Command Timeout of',
123
+ 'timed out due to inactivity',
124
+ // base-driver's defaults for `timeout` and `script timeout`.
125
+ 'did not complete before its timeout expired',
126
+ 'Timed out waiting for asynchronous script result',
127
+ // XCUITest and WebDriverAgent.
128
+ 'waiting for XCTest to complete',
129
+ 'cannot be launched within',
130
+ 'Timed out while waiting until the screen gets locked',
131
+ 'Did not receive any expected',
132
+ // UiAutomator2, when the app never lets the screen go idle (an endless
133
+ // animation, a video, a busy main thread) and it can't be read.
134
+ 'hogging the main UI thread',
135
+ // A test's own words (Selenium's exception).
136
+ 'TimeoutException',
137
+ ],
138
+ },
139
+ {
140
+ category: 'ELEMENT_NOT_FOUND',
141
+ codes: ['no such element'],
142
+ phrases: [
143
+ 'An element could not be located',
144
+ // WebDriverAgent's identifier and predicate lookups.
145
+ "didn't match any elements",
146
+ // A test's own words (NoSuchElementException).
147
+ 'NoSuchElement',
148
+ ],
149
+ },
150
+ {
151
+ // No native driver reports a blocking prompt: it shows up as Element not
152
+ // found. Only web pages on an iPhone, and a test's own words.
153
+ category: 'PERMISSION_BLOCKED',
154
+ codes: ['unexpected alert open'],
155
+ phrases: [
156
+ 'A modal dialog was open',
157
+ 'Permission alert',
158
+ 'Security alert',
159
+ 'Always Allow',
160
+ 'Allow while using app',
161
+ ],
162
+ },
163
+ ];
164
+ /** Every category the failure analysis writes, upper case as stored. */
165
+ exports.ANALYSIS_CATEGORIES = [
166
+ ...exports.FAILURE_RULES.map((r) => r.category),
167
+ exports.UNMATCHED_CATEGORY,
168
+ ];
169
+ /**
170
+ * Categories no longer written, which sessions filed through 2.15 still hold.
171
+ * Their runbooks stay. WDA_FAILURE's live cases are SESSION_LOST now, and
172
+ * XENON_COMMAND_FAILURE matched nothing real but base-driver's default
173
+ * stale-element text, which is STALE_ELEMENT.
174
+ */
175
+ exports.RETIRED_CATEGORIES = ['WDA_FAILURE', 'XENON_COMMAND_FAILURE'];
176
+ /**
177
+ * The category of a message Xenon itself throws, by how it starts. These
178
+ * quote the test's selector, which may hold any rule's phrase ("Allow
179
+ * notifications to be enabled"), so they are read before the rules.
180
+ */
181
+ function xenonOwnCategory(text) {
182
+ const t = text.trim();
183
+ // CommandInterceptor's autowait: an element that never became enabled, or
184
+ // one that was never found.
185
+ if (t.startsWith('Autowait timed out after ')) {
186
+ return t.endsWith(' to be enabled') ? 'TIMEOUT' : 'ELEMENT_NOT_FOUND';
187
+ }
188
+ // OmniVision's miss, and an element id Xenon doesn't hold.
189
+ if (t.startsWith('Xenon found nothing on the screen matching '))
190
+ return 'ELEMENT_NOT_FOUND';
191
+ if (t.startsWith('Xenon has no element '))
192
+ return 'ELEMENT_NOT_FOUND';
193
+ return undefined;
194
+ }
195
+ /**
196
+ * The error and message of a failed command's recorded answer, and nothing
197
+ * else: never its stack trace, which names netty's IdleStateHandler on every
198
+ * UiAutomator2 error and the driver on every error.
199
+ */
200
+ function commandErrorOf(response) {
201
+ var _a;
202
+ if (!response)
203
+ return {};
204
+ try {
205
+ const value = (_a = JSON.parse(response)) === null || _a === void 0 ? void 0 : _a.value;
206
+ if (!value || typeof value !== 'object')
207
+ return {};
208
+ return {
209
+ error: typeof value.error === 'string' ? value.error : undefined,
210
+ message: typeof value.message === 'string' ? value.message : undefined,
211
+ };
212
+ }
213
+ catch (_b) {
214
+ return {};
215
+ }
216
+ }
217
+ const norm = (s) => s.trim().toLowerCase();
218
+ function ruleFor(texts, codes) {
219
+ for (const t of texts) {
220
+ const own = xenonOwnCategory(t);
221
+ if (own)
222
+ return own;
223
+ }
224
+ const haystack = texts.map(norm);
225
+ const codeSet = new Set(codes.map(norm));
226
+ for (const rule of exports.FAILURE_RULES) {
227
+ if (rule.codes.some((c) => codeSet.has(norm(c))))
228
+ return rule.category;
229
+ if (rule.phrases.some((p) => haystack.some((t) => t.includes(norm(p)))))
230
+ return rule.category;
231
+ }
232
+ return undefined;
233
+ }
234
+ /**
235
+ * A failed session's category, from its failure reason and its failed
236
+ * commands (newest first).
237
+ *
238
+ * The failure that ended the session decides alone: the reason, read with the
239
+ * newest failed command when the reason was taken from it (for a session on
240
+ * a node's phone the reason is that command's bare code, "no such element",
241
+ * and its message says more). A reason no rule matches is UNKNOWN: an earlier
242
+ * failure the test recovered from, or a find it expected to fail, doesn't
243
+ * decide. Only a session with no reason at all is read from its failed
244
+ * commands, newest first.
245
+ */
246
+ function categorizeFailure(reason, failedCommands) {
247
+ const said = (c) => [c.error, c.message].filter((s) => !!s);
248
+ const codesOf = (c) => (c.error ? [c.error] : []);
249
+ const r = reason.trim();
250
+ if (r) {
251
+ const newest = failedCommands[0];
252
+ const fromNewest = !!newest && said(newest).some((s) => norm(s) === norm(r));
253
+ const category = fromNewest
254
+ ? ruleFor([r, ...said(newest)], codesOf(newest))
255
+ : ruleFor([r], [r]);
256
+ return category !== null && category !== void 0 ? category : exports.UNMATCHED_CATEGORY;
257
+ }
258
+ for (const c of failedCommands) {
259
+ const category = ruleFor(said(c), codesOf(c));
260
+ if (category)
261
+ return category;
262
+ }
263
+ return exports.UNMATCHED_CATEGORY;
264
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SESSION_COMMAND_SUMMARY_FIELDS = void 0;
4
+ exports.sessionCommandSummary = sessionCommandSummary;
5
+ /**
6
+ * The fields of a command's record that leave its session: the live
7
+ * `session_command` event and the event log carry these and nothing else.
8
+ * Which command ran in which session, how it went and how it healed. The rest
9
+ * stays in the session's SessionLog, which goes with the session: the
10
+ * record's `body` is the command's arguments (the text `setValue` typed) and
11
+ * its `response` the command's answer (a page source, a screenshot's base64).
12
+ * Named one by one, so a field added to the record later goes nowhere until
13
+ * it is listed here.
14
+ */
15
+ exports.SESSION_COMMAND_SUMMARY_FIELDS = [
16
+ 'session_id',
17
+ 'command_name',
18
+ 'method',
19
+ 'is_success',
20
+ 'is_error',
21
+ 'is_healed',
22
+ 'original_strategy',
23
+ 'original_selector',
24
+ 'healed_strategy',
25
+ 'healed_selector',
26
+ 'healing_confidence',
27
+ 'healing_tier',
28
+ 'duration',
29
+ 'span_id',
30
+ 'trace_id',
31
+ ];
32
+ function sessionCommandSummary(record) {
33
+ const summary = {};
34
+ for (const field of exports.SESSION_COMMAND_SUMMARY_FIELDS) {
35
+ if ((record === null || record === void 0 ? void 0 : record[field]) !== undefined)
36
+ summary[field] = record[field];
37
+ }
38
+ return summary;
39
+ }
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
24
  };
@@ -438,7 +449,9 @@ class LokiHealEtalonStore {
438
449
  const coll = yield this.getCollection();
439
450
  const existing = coll.findOne({ selector: etalon.selector });
440
451
  if (existing) {
441
- Object.assign(existing, etalon);
452
+ // A fingerprint saved without a path keeps the one stored, as in Prisma.
453
+ const { path } = etalon, rest = __rest(etalon, ["path"]);
454
+ Object.assign(existing, rest, path == null ? {} : { path });
442
455
  coll.update(existing);
443
456
  }
444
457
  else {
@@ -625,21 +625,24 @@ class PrismaHealEtalonStore {
625
625
  get prisma() {
626
626
  return typedi_1.Container.get(prisma_service_1.PrismaService).client;
627
627
  }
628
+ /**
629
+ * The fingerprint, and its path when it has one. A fingerprint saved without
630
+ * a path (a heal whose element's path couldn't be worked out) keeps the one
631
+ * stored. Through 2.14 the path was never stored, so the Resilio tier,
632
+ * which needs it, never found anything.
633
+ */
628
634
  saveSignature(etalon) {
629
635
  return __awaiter(this, void 0, void 0, function* () {
636
+ const path = etalon.path == null ? undefined : JSON.stringify(etalon.path);
630
637
  yield this.prisma.locatorEtalon.upsert({
631
638
  where: { selector: etalon.selector },
632
- update: {
633
- strategy: etalon.strategy,
634
- attributes: JSON.stringify(etalon.attributes),
635
- nodeName: etalon.nodeName,
636
- lastSeen: new Date(),
637
- },
639
+ update: Object.assign(Object.assign({ strategy: etalon.strategy, attributes: JSON.stringify(etalon.attributes), nodeName: etalon.nodeName }, (path !== undefined ? { path } : {})), { lastSeen: new Date() }),
638
640
  create: {
639
641
  selector: etalon.selector,
640
642
  strategy: etalon.strategy,
641
643
  attributes: JSON.stringify(etalon.attributes),
642
644
  nodeName: etalon.nodeName,
645
+ path,
643
646
  lastSeen: new Date(),
644
647
  },
645
648
  });
@@ -652,7 +655,16 @@ class PrismaHealEtalonStore {
652
655
  });
653
656
  if (!etalon)
654
657
  return null;
655
- return Object.assign(Object.assign({}, etalon), { attributes: JSON.parse(etalon.attributes), lastSeen: etalon.lastSeen.getTime() });
658
+ let path = null;
659
+ if (etalon.path) {
660
+ try {
661
+ path = JSON.parse(etalon.path);
662
+ }
663
+ catch (_a) {
664
+ path = null;
665
+ }
666
+ }
667
+ return Object.assign(Object.assign({}, etalon), { attributes: JSON.parse(etalon.attributes), path, lastSeen: etalon.lastSeen.getTime() });
656
668
  });
657
669
  }
658
670
  }
@@ -27,6 +27,13 @@ const SETTINGS = {
27
27
  buildCleanupMaxCount: 'number',
28
28
  buildCleanupSchedule: 'text',
29
29
  deleteBuildAssets: 'boolean',
30
+ aiProvider: 'text',
31
+ aiModel: 'text',
32
+ aiBaseUrl: 'text',
33
+ geminiModel: 'text',
34
+ openaiModel: 'text',
35
+ anthropicModel: 'text',
36
+ ollamaModel: 'text',
30
37
  };
31
38
  /**
32
39
  * The lab settings the dashboard saves, one `WebConfig` row each, keyed by the