@xenon-device-management/xenon 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,469 @@
1
+ <!doctype html>
2
+ <!--
3
+ A made-up shop app's checkout screen: what the sample phone "shows" in the
4
+ dashboard screenshots (device control's live preview, a session's recording).
5
+
6
+ 1344 x 2992 CSS px, the Pixel 8 Pro's screen. It is drawn at 448 x 997 dp and
7
+ scaled by 3, so the sizes below read like an Android layout. System fonts
8
+ only, so nothing is fetched.
9
+ -->
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ <meta name="viewport" content="width=1344, initial-scale=1" />
14
+ <title>Checkout</title>
15
+ <style>
16
+ :root {
17
+ --ink: #17201a;
18
+ --ink-2: #4c5a50;
19
+ --ink-3: #7b887f;
20
+ --line: #e3e9e4;
21
+ --paper: #f6f8f6;
22
+ --card: #ffffff;
23
+ --green: #16a34a;
24
+ --green-soft: #e6f6ec;
25
+ }
26
+ * {
27
+ box-sizing: border-box;
28
+ margin: 0;
29
+ padding: 0;
30
+ }
31
+ html,
32
+ body {
33
+ width: 1344px;
34
+ height: 2992px;
35
+ overflow: hidden;
36
+ background: var(--paper);
37
+ }
38
+ body {
39
+ font-family:
40
+ system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
41
+ color: var(--ink);
42
+ -webkit-font-smoothing: antialiased;
43
+ }
44
+ .screen {
45
+ width: 448px;
46
+ height: 997.34px;
47
+ transform: scale(3);
48
+ transform-origin: 0 0;
49
+ position: relative;
50
+ display: flex;
51
+ flex-direction: column;
52
+ background: var(--paper);
53
+ }
54
+
55
+ .status {
56
+ height: 30px;
57
+ padding: 0 22px;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ font-size: 13px;
62
+ font-weight: 600;
63
+ background: var(--card);
64
+ }
65
+ .status .icons {
66
+ display: flex;
67
+ gap: 6px;
68
+ align-items: center;
69
+ }
70
+ .status svg {
71
+ display: block;
72
+ }
73
+
74
+ .appbar {
75
+ height: 58px;
76
+ padding: 0 16px;
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 14px;
80
+ background: var(--card);
81
+ border-bottom: 1px solid var(--line);
82
+ }
83
+ .appbar h1 {
84
+ font-size: 20px;
85
+ font-weight: 600;
86
+ flex: 1;
87
+ }
88
+ .secure {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 4px;
92
+ font-size: 12px;
93
+ color: var(--green);
94
+ font-weight: 600;
95
+ }
96
+
97
+ .content {
98
+ flex: 1;
99
+ padding: 14px 16px 0;
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 12px;
103
+ }
104
+ .card {
105
+ background: var(--card);
106
+ border: 1px solid var(--line);
107
+ border-radius: 14px;
108
+ padding: 14px 16px;
109
+ }
110
+ .eyebrow {
111
+ font-size: 11px;
112
+ font-weight: 700;
113
+ letter-spacing: 0.06em;
114
+ text-transform: uppercase;
115
+ color: var(--ink-3);
116
+ }
117
+
118
+ .deliver {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 12px;
122
+ }
123
+ .deliver .pin {
124
+ width: 38px;
125
+ height: 38px;
126
+ border-radius: 50%;
127
+ background: var(--green-soft);
128
+ display: grid;
129
+ place-items: center;
130
+ flex: none;
131
+ }
132
+ .deliver b {
133
+ font-size: 16px;
134
+ display: block;
135
+ margin-top: 2px;
136
+ }
137
+ .deliver span {
138
+ font-size: 13px;
139
+ color: var(--ink-2);
140
+ }
141
+ .deliver .change {
142
+ margin-left: auto;
143
+ font-size: 13px;
144
+ font-weight: 600;
145
+ color: var(--green);
146
+ }
147
+
148
+ .items {
149
+ padding: 0;
150
+ }
151
+ .items .head {
152
+ padding: 14px 16px 6px;
153
+ display: flex;
154
+ justify-content: space-between;
155
+ align-items: baseline;
156
+ }
157
+ .items .head b {
158
+ font-size: 16px;
159
+ }
160
+ .item {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 14px;
164
+ padding: 10px 16px;
165
+ border-top: 1px solid var(--line);
166
+ }
167
+ .thumb {
168
+ width: 54px;
169
+ height: 54px;
170
+ border-radius: 12px;
171
+ flex: none;
172
+ display: grid;
173
+ place-items: center;
174
+ }
175
+ .item .name {
176
+ font-size: 15px;
177
+ font-weight: 600;
178
+ }
179
+ .item .meta {
180
+ font-size: 12.5px;
181
+ color: var(--ink-3);
182
+ margin-top: 2px;
183
+ }
184
+ .item .price {
185
+ margin-left: auto;
186
+ font-size: 15px;
187
+ font-weight: 600;
188
+ font-variant-numeric: tabular-nums;
189
+ }
190
+ .item .main {
191
+ min-width: 0;
192
+ }
193
+
194
+ .promo {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 10px;
198
+ font-size: 14px;
199
+ }
200
+ .promo .tag {
201
+ font-weight: 700;
202
+ font-size: 12px;
203
+ letter-spacing: 0.04em;
204
+ color: var(--green);
205
+ background: var(--green-soft);
206
+ border-radius: 7px;
207
+ padding: 4px 8px;
208
+ }
209
+ .promo .off {
210
+ margin-left: auto;
211
+ color: var(--green);
212
+ font-weight: 600;
213
+ font-variant-numeric: tabular-nums;
214
+ }
215
+
216
+ .totals {
217
+ display: flex;
218
+ flex-direction: column;
219
+ gap: 7px;
220
+ font-size: 14px;
221
+ color: var(--ink-2);
222
+ }
223
+ .totals .row {
224
+ display: flex;
225
+ justify-content: space-between;
226
+ font-variant-numeric: tabular-nums;
227
+ }
228
+ .totals .row.total {
229
+ margin-top: 4px;
230
+ padding-top: 10px;
231
+ border-top: 1px solid var(--line);
232
+ font-size: 18px;
233
+ font-weight: 700;
234
+ color: var(--ink);
235
+ }
236
+
237
+ .pay-method {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 12px;
241
+ border: 1.5px solid var(--green);
242
+ background: var(--green-soft);
243
+ border-radius: 12px;
244
+ padding: 12px 14px;
245
+ margin-top: 10px;
246
+ }
247
+ .pay-method.plain {
248
+ border-color: var(--line);
249
+ background: var(--card);
250
+ color: var(--ink-2);
251
+ font-size: 14px;
252
+ font-weight: 600;
253
+ }
254
+ .radio {
255
+ width: 20px;
256
+ height: 20px;
257
+ border-radius: 50%;
258
+ border: 2px solid var(--green);
259
+ display: grid;
260
+ place-items: center;
261
+ flex: none;
262
+ }
263
+ .radio::after {
264
+ content: '';
265
+ width: 10px;
266
+ height: 10px;
267
+ border-radius: 50%;
268
+ background: var(--green);
269
+ }
270
+ .radio.off {
271
+ border-color: var(--ink-3);
272
+ }
273
+ .radio.off::after {
274
+ display: none;
275
+ }
276
+ .card-chip {
277
+ width: 40px;
278
+ height: 28px;
279
+ border-radius: 6px;
280
+ background: linear-gradient(135deg, #1d4ed8, #3b82f6);
281
+ flex: none;
282
+ }
283
+ .pay-method b {
284
+ font-size: 15px;
285
+ display: block;
286
+ }
287
+ .pay-method span {
288
+ font-size: 12.5px;
289
+ color: var(--ink-2);
290
+ }
291
+
292
+ .footer {
293
+ padding: 12px 16px 10px;
294
+ background: var(--card);
295
+ border-top: 1px solid var(--line);
296
+ }
297
+ .pay-now {
298
+ height: 56px;
299
+ border-radius: 16px;
300
+ background: var(--green);
301
+ color: #fff;
302
+ font-size: 18px;
303
+ font-weight: 700;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: center;
307
+ gap: 10px;
308
+ }
309
+ .footer .fine {
310
+ text-align: center;
311
+ font-size: 11.5px;
312
+ color: var(--ink-3);
313
+ margin-top: 8px;
314
+ }
315
+ .gesture {
316
+ height: 24px;
317
+ background: var(--card);
318
+ display: grid;
319
+ place-items: center;
320
+ }
321
+ .gesture i {
322
+ width: 120px;
323
+ height: 4px;
324
+ border-radius: 2px;
325
+ background: #1c2620;
326
+ opacity: 0.8;
327
+ }
328
+ </style>
329
+ </head>
330
+ <body>
331
+ <div class="screen">
332
+ <div class="status">
333
+ <span>10:42</span>
334
+ <span class="icons">
335
+ <svg width="16" height="12" viewBox="0 0 16 12" aria-hidden="true">
336
+ <path d="M8 11.5 0.8 3.3a10.4 10.4 0 0 1 14.4 0z" fill="#17201a" />
337
+ </svg>
338
+ <svg width="16" height="12" viewBox="0 0 16 12" aria-hidden="true">
339
+ <path d="M15 11V1L1 11z" fill="#17201a" />
340
+ </svg>
341
+ <svg width="26" height="12" viewBox="0 0 26 12" aria-hidden="true">
342
+ <rect x="0.5" y="0.5" width="22" height="11" rx="3" fill="none" stroke="#17201a" />
343
+ <rect x="2" y="2" width="16" height="8" rx="1.6" fill="#17201a" />
344
+ <rect x="23.5" y="4" width="2" height="4" rx="1" fill="#17201a" />
345
+ </svg>
346
+ </span>
347
+ </div>
348
+
349
+ <div class="appbar">
350
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
351
+ <path
352
+ d="M15 5 8 12l7 7"
353
+ stroke="#17201a"
354
+ stroke-width="2.4"
355
+ stroke-linecap="round"
356
+ stroke-linejoin="round"
357
+ />
358
+ </svg>
359
+ <h1>Checkout</h1>
360
+ <span class="secure">
361
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden="true">
362
+ <rect x="5" y="10.5" width="14" height="10" rx="2.4" fill="#16a34a" />
363
+ <path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" stroke="#16a34a" stroke-width="2.4" />
364
+ </svg>
365
+ Secure
366
+ </span>
367
+ </div>
368
+
369
+ <div class="content">
370
+ <div class="card deliver">
371
+ <div class="pin">
372
+ <svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
373
+ <path
374
+ d="M12 2.5a7 7 0 0 0-7 7c0 5.2 7 12 7 12s7-6.8 7-12a7 7 0 0 0-7-7zm0 9.4a2.4 2.4 0 1 1 0-4.8 2.4 2.4 0 0 1 0 4.8z"
375
+ fill="#16a34a"
376
+ />
377
+ </svg>
378
+ </div>
379
+ <div>
380
+ <div class="eyebrow">Deliver to</div>
381
+ <b>Priya Sharma</b>
382
+ <span>12 Example Street, Springfield</span>
383
+ </div>
384
+ <span class="change">Change</span>
385
+ </div>
386
+
387
+ <div class="card items">
388
+ <div class="head">
389
+ <b>Order summary</b>
390
+ <span class="eyebrow">3 items</span>
391
+ </div>
392
+ <div class="item">
393
+ <div class="thumb" style="background: linear-gradient(135deg, #bbf7d0, #4ade80)">
394
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" aria-hidden="true">
395
+ <path
396
+ d="M3 16.5c0-3 2-4 4-4.2l2.4-3.8 2.6 2.4c2 .2 5 1 7 3.6 1 1.3 1 2.3 0 2.3H4.5C3.6 16.8 3 16.8 3 16.5z"
397
+ fill="#14532d"
398
+ />
399
+ </svg>
400
+ </div>
401
+ <div class="main">
402
+ <div class="name">Trail running shoes</div>
403
+ <div class="meta">Size 42 &middot; Forest &middot; Qty 1</div>
404
+ </div>
405
+ <div class="price">$89.00</div>
406
+ </div>
407
+ <div class="item">
408
+ <div class="thumb" style="background: linear-gradient(135deg, #fde68a, #fbbf24)">
409
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" aria-hidden="true">
410
+ <path d="M8 3h7v8l3.5 7a2.2 2.2 0 0 1-2 3.2H9.5A3.2 3.2 0 0 1 6.4 18z" fill="#78350f" />
411
+ </svg>
412
+ </div>
413
+ <div class="main">
414
+ <div class="name">Merino hiking socks</div>
415
+ <div class="meta">2-pack &middot; M &middot; Qty 1</div>
416
+ </div>
417
+ <div class="price">$24.00</div>
418
+ </div>
419
+ <div class="item">
420
+ <div class="thumb" style="background: linear-gradient(135deg, #bfdbfe, #60a5fa)">
421
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" aria-hidden="true">
422
+ <rect x="9.5" y="2.5" width="5" height="3" rx="1" fill="#1e3a8a" />
423
+ <rect x="7" y="6" width="10" height="15.5" rx="3" fill="#1e3a8a" />
424
+ </svg>
425
+ </div>
426
+ <div class="main">
427
+ <div class="name">Insulated bottle 750 ml</div>
428
+ <div class="meta">Slate &middot; Qty 1</div>
429
+ </div>
430
+ <div class="price">$32.50</div>
431
+ </div>
432
+ </div>
433
+
434
+ <div class="card promo">
435
+ <span class="tag">SPRING10</span>
436
+ <span>Promo code applied</span>
437
+ <span class="off">&minus;$14.55</span>
438
+ </div>
439
+
440
+ <div class="card">
441
+ <div class="totals">
442
+ <div class="row"><span>Subtotal</span><span>$145.50</span></div>
443
+ <div class="row"><span>Shipping</span><span>Free</span></div>
444
+ <div class="row"><span>Discount</span><span>&minus;$14.55</span></div>
445
+ <div class="row total"><span>Total</span><span>$130.95</span></div>
446
+ </div>
447
+ <div class="pay-method">
448
+ <div class="radio"></div>
449
+ <div class="card-chip"></div>
450
+ <div>
451
+ <b>Visa &bull;&bull;&bull;&bull; 4242</b>
452
+ <span>Saved card &middot; expires 08/28</span>
453
+ </div>
454
+ </div>
455
+ <div class="pay-method plain">
456
+ <div class="radio off"></div>
457
+ <span style="color: var(--ink-2); font-size: 14px; font-weight: 600">Add a new card</span>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ <div class="footer">
463
+ <div class="pay-now">Pay now &middot; $130.95</div>
464
+ <div class="fine">Your card is charged when you tap Pay now.</div>
465
+ </div>
466
+ <div class="gesture"><i></i></div>
467
+ </div>
468
+ </body>
469
+ </html>
@@ -0,0 +1,242 @@
1
+ /**
2
+ * A sample finished session for the dashboard screenshots: a made-up
3
+ * "Checkout: pay by saved card" test that passed on the Pixel 8 Pro, with its
4
+ * build, command log, device and debug logs and CPU and memory figures.
5
+ *
6
+ * Every time is a fixed instant, so the pictures don't change from run to run
7
+ * (the script gives the browser the UTC time zone, so they don't change from
8
+ * machine to machine either).
9
+ */
10
+
11
+ const SESSION_ID = 'f3a91c5e-7d2b-4e08-9a64-0b8c1d2e3f47';
12
+ const BUILD_ID = 'bld-42';
13
+ const UDID = '28141FDH2000AB';
14
+ const APP = 'com.example.shop';
15
+ /** The recording's file name; the script serves a WebM under it. */
16
+ const VIDEO_FILE = 'session.webm';
17
+
18
+ /** 2026-10-04 02:14:09 UTC. */
19
+ const START = Date.UTC(2026, 9, 4, 2, 14, 9);
20
+ /** The session lasted 2 min 38 s. */
21
+ const DURATION_MS = 158_500;
22
+ const iso = (ms) => new Date(ms).toISOString();
23
+
24
+ const BUILD = {
25
+ id: BUILD_ID,
26
+ name: 'nightly-2026-10-04',
27
+ createdAt: iso(START - 4 * 60_000),
28
+ updatedAt: iso(START + DURATION_MS),
29
+ sessionCount: 1,
30
+ passedCount: 1,
31
+ failedCount: 0,
32
+ runningCount: 0,
33
+ };
34
+
35
+ const CAPABILITIES = {
36
+ platformName: 'Android',
37
+ 'appium:automationName': 'UiAutomator2',
38
+ 'appium:deviceName': 'Pixel 8 Pro',
39
+ 'appium:platformVersion': '15',
40
+ 'appium:appPackage': APP,
41
+ 'appium:appActivity': '.home.HomeActivity',
42
+ 'appium:newCommandTimeout': 120,
43
+ 'xe:options': { name: 'Checkout: pay by saved card', build: 'nightly-2026-10-04' },
44
+ };
45
+
46
+ const SESSION = {
47
+ id: SESSION_ID,
48
+ build_id: BUILD_ID,
49
+ name: 'Checkout: pay by saved card',
50
+ status: 'passed',
51
+ desired_capabilities: JSON.stringify(CAPABILITIES),
52
+ session_capabilities: JSON.stringify({
53
+ ...CAPABILITIES,
54
+ 'appium:udid': UDID,
55
+ 'appium:deviceScreenSize': '1344x2992',
56
+ 'appium:deviceApiLevel': 35,
57
+ }),
58
+ node_id: 'lab-mac-01',
59
+ has_live_video: false,
60
+ video_recording_enabled: true,
61
+ video_recording: `${SESSION_ID}/video/${VIDEO_FILE}`,
62
+ startTime: iso(START),
63
+ endTime: iso(START + DURATION_MS),
64
+ failure_reason: null,
65
+ failure_category: null,
66
+ ai_analysis: null,
67
+ tags: null,
68
+ device_udid: UDID,
69
+ device_platform: 'android',
70
+ device_version: '15',
71
+ device_name: 'Pixel 8 Pro',
72
+ performance_trace: null,
73
+ createdAt: iso(START),
74
+ updatedAt: iso(START + DURATION_MS),
75
+ owner: { name: 'Priya', email: 'priya@example.com' },
76
+ ranOn: 'here',
77
+ };
78
+
79
+ /**
80
+ * [end offset in seconds, command, duration in ms, extra fields]: oldest
81
+ * first. A row is written as its command ends. The one healed command is a
82
+ * find whose id changed from `btn_pay` to `btn_pay_now`.
83
+ */
84
+ const STEPS = [
85
+ [9.4, 'findElement', 410, { body: ['id', `${APP}:id/cart_checkout`] }],
86
+ [10.9, 'click', 92, {}],
87
+ [24.7, 'findElement', 288, { body: ['id', `${APP}:id/saved_card_visa`] }],
88
+ [26.2, 'click', 84, {}],
89
+ [38.8, 'getText', 71, {}],
90
+ [
91
+ 55.3,
92
+ 'findElement',
93
+ 1942,
94
+ {
95
+ body: ['id', `${APP}:id/btn_pay`],
96
+ healed: {
97
+ from: ['id', `${APP}:id/btn_pay`],
98
+ to: ['id', `${APP}:id/btn_pay_now`],
99
+ tier: 'Fuzzy XML',
100
+ confidence: 0.91,
101
+ },
102
+ },
103
+ ],
104
+ [57.0, 'click', 104, {}],
105
+ [88.6, 'findElement', 11_206, { body: ['id', `${APP}:id/order_confirmation`] }],
106
+ [90.2, 'getText', 64, {}],
107
+ [96.5, 'getScreenshot', 188, {}],
108
+ [112.0, 'findElement', 297, { body: ['id', `${APP}:id/order_number`] }],
109
+ [113.5, 'getText', 58, {}],
110
+ [131.8, 'getPageSource', 641, {}],
111
+ [158.4, 'deleteSession', 1204, {}],
112
+ ];
113
+
114
+ const titleOf = (name) => name.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase());
115
+ const pathOf = (name) =>
116
+ ({
117
+ findElement: '/element',
118
+ click: '/element/e1/click',
119
+ getText: '/element/e1/text',
120
+ getScreenshot: '/screenshot',
121
+ getPageSource: '/source',
122
+ deleteSession: '',
123
+ })[name] ?? '';
124
+
125
+ /** The command log as GET /session/:id/session_log answers it: newest first. */
126
+ const COMMANDS = STEPS.map(([end, name, duration, extra], i) => {
127
+ const at = iso(START + Math.round(end * 1000));
128
+ const heal = extra.healed;
129
+ return {
130
+ id: `cmd-${String(i + 1).padStart(2, '0')}`,
131
+ session_id: SESSION_ID,
132
+ command_name: name,
133
+ url: `/wd/hub/session/${SESSION_ID}${pathOf(name)}`,
134
+ method:
135
+ name === 'deleteSession'
136
+ ? 'DELETE'
137
+ : name === 'getText' || name === 'getPageSource' || name === 'getScreenshot'
138
+ ? 'GET'
139
+ : 'POST',
140
+ title: titleOf(name),
141
+ subtitle: '',
142
+ body: JSON.stringify(extra.body ?? {}),
143
+ response: JSON.stringify({ value: null }),
144
+ screenshot: null,
145
+ is_success: true,
146
+ is_error: false,
147
+ is_healed: !!heal,
148
+ original_strategy: heal ? heal.from[0] : (extra.body?.[0] ?? null),
149
+ original_selector: heal ? heal.from[1] : (extra.body?.[1] ?? null),
150
+ healed_strategy: heal ? heal.to[0] : null,
151
+ healed_selector: heal ? heal.to[1] : null,
152
+ healing_tier: heal ? heal.tier : null,
153
+ healing_confidence: heal ? heal.confidence : null,
154
+ duration,
155
+ createdAt: at,
156
+ updatedAt: at,
157
+ };
158
+ }).reverse();
159
+
160
+ const LOG_LINES = [
161
+ [1.2, 'DEVICE', 'I/ActivityTaskManager: START u0 {cmp=com.example.shop/.home.HomeActivity}'],
162
+ [8.7, 'DEVICE', 'D/ShopCheckout: CartViewModel: 3 items, subtotal=145.50 USD'],
163
+ [52.1, 'DEVICE', 'W/chromium: [WARNING:CONSOLE(1)] "Slow payment: 9.3s from tap to capture"'],
164
+ [88.0, 'DEVICE', 'I/ShopCheckout: payment captured paymentId=pay_3f9a2c attempt=3'],
165
+ [0.4, 'DEBUG', 'Session started on Pixel 8 Pro (Android 15)'],
166
+ [
167
+ 55.2,
168
+ 'DEBUG',
169
+ 'Selector id=com.example.shop:id/btn_pay not found; healed by Fuzzy XML to id=com.example.shop:id/btn_pay_now (91%)',
170
+ ],
171
+ [158.2, 'DEBUG', 'Session finished: passed'],
172
+ ];
173
+ const logRows = (type) =>
174
+ LOG_LINES.filter(([, t]) => t === type).map(([at, t, message], i) => ({
175
+ id: `log-${type.toLowerCase()}-${i + 1}`,
176
+ session_id: SESSION_ID,
177
+ log_type: t,
178
+ message,
179
+ timestamp: iso(START + Math.round(at * 1000)),
180
+ createdAt: iso(START + Math.round(at * 1000)),
181
+ updatedAt: iso(START + Math.round(at * 1000)),
182
+ }));
183
+ const DEVICE_LOGS = logRows('DEVICE');
184
+ const DEBUG_LOGS = logRows('DEBUG');
185
+
186
+ const clamp = (lo, hi, v) => Math.min(hi, Math.max(lo, v));
187
+ const round1 = (v) => Math.round(v * 10) / 10;
188
+ /** A smooth bump: `height` at sample `at`, fading over about `width` samples. */
189
+ const bump = (i, at, width, height) => height * Math.exp(-(((i - at) / width) ** 2));
190
+
191
+ /**
192
+ * 80 samples, one every 2 s, as GET /session/:id/metrics answers them. The
193
+ * device's CPU stays between 15 and 45 %, the app's between 5 and 25 %,
194
+ * with a rise as the app opens and another as the payment goes through. The
195
+ * first sample has no CPU: it comes from the change since the one before.
196
+ */
197
+ const SAMPLES = Array.from({ length: 80 }, (_, i) => {
198
+ const deviceCpu =
199
+ 27 +
200
+ 7 * Math.sin(i * 0.18) +
201
+ 3 * Math.sin(i * 0.55 + 1.3) +
202
+ bump(i, 4, 3, 7) +
203
+ bump(i, 43, 5, 9);
204
+ const appCpu =
205
+ 11 +
206
+ 4 * Math.sin(i * 0.21 + 0.7) +
207
+ 2 * Math.sin(i * 0.6) +
208
+ bump(i, 5, 3, 7) +
209
+ bump(i, 43, 5, 6);
210
+ const progress = i / 79;
211
+ return {
212
+ t: START + 300 + i * 2000,
213
+ deviceCpu: i === 0 ? null : round1(clamp(15, 45, deviceCpu)),
214
+ deviceMemMb: Math.round(5320 + 24 * Math.sin(i * 0.13) + 0.9 * i),
215
+ deviceMemTotalMb: 11776,
216
+ appCpu: i === 0 ? null : round1(clamp(5, 25, appCpu)),
217
+ appMemMb: round1(290 + 50 * (progress * (2 - progress)) + 2 * Math.sin(i * 0.9)),
218
+ app: APP,
219
+ };
220
+ });
221
+
222
+ const METRICS = {
223
+ platform: 'android',
224
+ intervalMs: 2000,
225
+ appId: APP,
226
+ series: { deviceCpu: true, deviceMem: true, appCpu: true, appMem: true },
227
+ recording: null,
228
+ samples: SAMPLES,
229
+ };
230
+
231
+ module.exports = {
232
+ SESSION_ID,
233
+ BUILD_ID,
234
+ UDID,
235
+ VIDEO_FILE,
236
+ BUILD,
237
+ SESSION,
238
+ COMMANDS,
239
+ DEVICE_LOGS,
240
+ DEBUG_LOGS,
241
+ METRICS,
242
+ };