@xenon-device-management/xenon 2.15.0 → 2.17.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 (206) hide show
  1. package/README.md +38 -26
  2. package/lib/package.json +2 -1
  3. package/lib/public/assets/{AnnotationOverlay-Btq3dZRI.js → AnnotationOverlay-CjmMCS0S.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-C9OoAgg_.js → ApiKeyGate-B4Ob0v3C.js} +1 -1
  5. package/lib/public/assets/{DeviceMosaicView-DBC_xczB.js → DeviceMosaicView-DTVOB_NO.js} +1 -1
  6. package/lib/public/assets/{EmptyState-DqWU2-vM.js → EmptyState-DcEG-oAi.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-CveQ3xrA.js → FieldGroup-CtnCDshE.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BfKDSfHz.js → FilterMenu-BK0dE9cc.js} +1 -1
  9. package/lib/public/assets/{Menu-CtdCybzA.js → Menu-eKsmLpKS.js} +1 -1
  10. package/lib/public/assets/{Modal-Bsvj1tm2.js → Modal-DPwop3Lz.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CRofOiyx.js → RecordingPage-CfgSkZkB.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-jNQHb2IR.js → RecordingsPage-Bzl9pTUY.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-C8HtTha-.js → SegmentedControl-B_TZTrPm.js} +1 -1
  14. package/lib/public/assets/{SettingCard-D8K1LZNo.js → SettingCard-DlmvZJPM.js} +1 -1
  15. package/lib/public/assets/{Table-zNTX92Pn.js → Table-BigPWKXi.js} +1 -1
  16. package/lib/public/assets/{activity-CJ8LwZq9.js → activity-CvuZdQYu.js} +1 -1
  17. package/lib/public/assets/{ai-settings-CgxQZqrc.js → ai-settings-lI2UJ2n7.js} +1 -1
  18. package/lib/public/assets/{api-keys-ECpo-zTo.js → api-keys-CPgf9XJM.js} +1 -1
  19. package/lib/public/assets/{apps-BjGymyf3.js → apps-DIzOvtUp.js} +1 -1
  20. package/lib/public/assets/{arrow-left-BWfCbeHP.js → arrow-left-DiP5ywBT.js} +1 -1
  21. package/lib/public/assets/{arrow-right-DC7UGf_n.js → arrow-right-DtW_8hm5.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-BOjwybDk.js → arrow-up-right-C89FbNU0.js} +1 -1
  23. package/lib/public/assets/{auth-shell-CxMTo9aH.js → auth-shell-B7sUDb--.js} +2 -2
  24. package/lib/public/assets/{builds-page-DOCm4HkA.js → builds-page-BBhvrMfA.js} +1 -1
  25. package/lib/public/assets/{button-Cg08-EMT.js → button-DugsdUw1.js} +1 -1
  26. package/lib/public/assets/{calendar-CA1ikLJs.js → calendar-grKd08ZH.js} +1 -1
  27. package/lib/public/assets/{check-BMTaH72W.js → check-ORjyNfnX.js} +1 -1
  28. package/lib/public/assets/{chevron-right-B2QOv9Nx.js → chevron-right-Ei2FxUXq.js} +1 -1
  29. package/lib/public/assets/{circle-check-CYXBoUqT.js → circle-check-Day0HjW4.js} +1 -1
  30. package/lib/public/assets/{circle-x-DTwnSAnl.js → circle-x-CHxSRLHp.js} +1 -1
  31. package/lib/public/assets/{clock-C8_HViQP.js → clock-BDHewWTx.js} +1 -1
  32. package/lib/public/assets/{copy-Be7LNMXV.js → copy-CTwT4I3D.js} +1 -1
  33. package/lib/public/assets/{device-explorer-BOqtfGXz.js → device-explorer-DlhrKJ-g.js} +1 -1
  34. package/lib/public/assets/{download-B56lwxWP.js → download-D3bjz_vr.js} +1 -1
  35. package/lib/public/assets/{forgot-password-CCU6bzLn.js → forgot-password-BNBLi023.js} +1 -1
  36. package/lib/public/assets/{h264Stream-C-OtmIuk.js → h264Stream-C51Au2i_.js} +1 -1
  37. package/lib/public/assets/index-Dk5J6HB1.js +219 -0
  38. package/lib/public/assets/{index-B9KQ6LsY.js → index-DncSz5LG.js} +1 -1
  39. package/lib/public/assets/{index-CfxsUslp.js → index-Z7_BY3pJ.js} +1 -1
  40. package/lib/public/assets/{input-CZHFjbSg.js → input-bTXM4l09.js} +1 -1
  41. package/lib/public/assets/{line-chart-Gl_Dz3X_.js → line-chart-B3YmVYyr.js} +1 -1
  42. package/lib/public/assets/{list-checks-CCgjeyGY.js → list-checks-BClKEj1-.js} +1 -1
  43. package/lib/public/assets/{lock-CkOwgDcx.js → lock-CZsenX3D.js} +1 -1
  44. package/lib/public/assets/{login-D5cc4w7M.js → login-B6A6tOp9.js} +1 -1
  45. package/lib/public/assets/{maintenance-settings-t7qr3fvX.js → maintenance-settings-CmCJGy7O.js} +1 -1
  46. package/lib/public/assets/{monitor-DEJixKQO.js → monitor-BdTNEglU.js} +1 -1
  47. package/lib/public/assets/{mouse-pointer-2-CtOrk4M8.js → mouse-pointer-2-BM8CaLtt.js} +1 -1
  48. package/lib/public/assets/{network-Dii9qdvk.js → network-BS6vUc1x.js} +1 -1
  49. package/lib/public/assets/{overview-rolAACrO.js → overview-Yfk_Y7g3.js} +1 -1
  50. package/lib/public/assets/{page-header-COQZxJfR.js → page-header-DhXCwpml.js} +1 -1
  51. package/lib/public/assets/{play-DrN1Se_R.js → play-D08gP1td.js} +1 -1
  52. package/lib/public/assets/{plus-5aq-9uRx.js → plus-BKboorfa.js} +1 -1
  53. package/lib/public/assets/{profile-page-6i5kY6kF.js → profile-page-Cd9NodrA.js} +1 -1
  54. package/lib/public/assets/{reset-password-sp2IBrc7.js → reset-password-acLDyyDW.js} +1 -1
  55. package/lib/public/assets/{runbook-page-DJDjeopU.js → runbook-page-CWLkC1Fd.js} +153 -102
  56. package/lib/public/assets/{select-Bpyn7rWO.js → select-DZoRKNti.js} +1 -1
  57. package/lib/public/assets/{selector-detail-redirect-BH74vmKv.js → selector-detail-redirect-DS4qrt84.js} +1 -1
  58. package/lib/public/assets/{selector-health-page-Cw_g218H.js → selector-health-page-TpmN61G3.js} +1 -1
  59. package/lib/public/assets/session-detail-page-C0wYaXqy.js +4 -0
  60. package/lib/public/assets/{settings-C3uyGhyc.js → settings-CxPGEY2j.js} +1 -1
  61. package/lib/public/assets/{stat-tile-xVqAKIlI.js → stat-tile-DUpyYgl-.js} +1 -1
  62. package/lib/public/assets/{tablet-BxUScAeO.js → tablet-SA02BJNQ.js} +1 -1
  63. package/lib/public/assets/{teams-B5ljGkY2.js → teams-jamD0Kxr.js} +1 -1
  64. package/lib/public/assets/{trash-2-B701i8sy.js → trash-2-DO8fv6w4.js} +1 -1
  65. package/lib/public/assets/{upload-C2gr44RS.js → upload-CaiGCStb.js} +1 -1
  66. package/lib/public/assets/{use-builds-data-NeQOFEoy.js → use-builds-data-jS-fcvVf.js} +1 -1
  67. package/lib/public/assets/{use-password-reset-mode-DEzJq5ef.js → use-password-reset-mode-BzoenegM.js} +1 -1
  68. package/lib/public/assets/{users-MY6eNe6s.js → users-Bnw7Vs_B.js} +1 -1
  69. package/lib/public/assets/{users-D_vv_yz4.js → users-DHvDrriG.js} +1 -1
  70. package/lib/public/assets/{video-off-OK1lNZQn.js → video-off-FEvy0w5U.js} +1 -1
  71. package/lib/public/assets/{webhook-settings-BU-C9lBf.js → webhook-settings-CBVB5lxi.js} +1 -1
  72. package/lib/public/assets/{zap-DO9ry1iz.js → zap-BMrTJO6M.js} +1 -1
  73. package/lib/public/index.html +1 -1
  74. package/lib/schema.json +11 -7
  75. package/lib/src/InternalHttpClient.js +27 -5
  76. package/lib/src/XenonCapabilityManager.js +10 -3
  77. package/lib/src/app/index.js +2 -0
  78. package/lib/src/app/openapi/grid.yaml +102 -0
  79. package/lib/src/app/openapi/sessions.yaml +7 -5
  80. package/lib/src/app/routers/auth.js +2 -1
  81. package/lib/src/app/routers/users.js +2 -1
  82. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  83. package/lib/src/dashboard/commandLogFields.js +42 -0
  84. package/lib/src/dashboard/event-manager.js +23 -154
  85. package/lib/src/dashboard/services/failure-analysis-service.js +8 -86
  86. package/lib/src/dashboard/services/failureCategories.js +264 -0
  87. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  88. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  89. package/lib/src/device-utils.js +25 -8
  90. package/lib/src/gateway/forwardToNode.js +4 -11
  91. package/lib/src/gateway/hubSessionToken.js +27 -4
  92. package/lib/src/gateway/nodeAsk.js +57 -0
  93. package/lib/src/gateway/nodeSessionDeviceLogs.js +35 -0
  94. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  95. package/lib/src/helpers/outboundProxy.js +219 -0
  96. package/lib/src/index.js +21 -0
  97. package/lib/src/interceptors/CommandInterceptor.js +13 -15
  98. package/lib/src/interceptors/xenonScripts.js +28 -7
  99. package/lib/src/logger.js +3 -1
  100. package/lib/src/plugin.js +12 -4
  101. package/lib/src/scripts/database-check.js +3 -3
  102. package/lib/src/scripts/initialize-database.js +29 -33
  103. package/lib/src/scripts/run-migrations.js +320 -25
  104. package/lib/src/services/ApiKeyService.js +16 -5
  105. package/lib/src/services/InterceptorService.js +6 -2
  106. package/lib/src/services/OrphanSweeper.js +6 -0
  107. package/lib/src/services/SelectorStateService.js +11 -7
  108. package/lib/src/services/SelectorVerificationJob.js +6 -2
  109. package/lib/src/services/SessionLifecycleService.js +52 -23
  110. package/lib/src/services/SocketClient.js +11 -0
  111. package/lib/src/services/SocketServer.js +361 -42
  112. package/lib/src/services/TeamService.js +5 -1
  113. package/lib/src/services/TracingService.js +171 -60
  114. package/lib/src/services/UserService.js +14 -3
  115. package/lib/src/services/UserSessionService.js +16 -4
  116. package/lib/src/services/healing/HealingOrchestrator.js +3 -1
  117. package/lib/src/services/identity/identityChanges.js +41 -0
  118. package/lib/src/services/interceptor/CertManager.js +50 -16
  119. package/lib/src/services/logcat/NodeDeviceLogStore.js +99 -0
  120. package/lib/src/services/logcat/NodeDeviceLogsCollector.js +164 -0
  121. package/lib/src/services/logcat/SessionDeviceLogs.js +279 -37
  122. package/lib/src/services/logcat/deviceLogBook.js +5 -4
  123. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  124. package/lib/src/services/logcat/nodeDeviceLogs.js +91 -0
  125. package/lib/src/services/metrics/nodeMetrics.js +20 -49
  126. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  127. package/lib/src/services/token/JwtKeyService.js +8 -2
  128. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  129. package/lib/src/sessions/LocalSession.js +29 -1
  130. package/lib/src/sessions/RemoteSession.js +23 -4
  131. package/lib/src/sessions/SessionManager.js +33 -9
  132. package/lib/test/helpers/driver-log.js +38 -0
  133. package/lib/test/helpers/fake-http-proxy.js +106 -0
  134. package/lib/test/helpers/fake-logcat.js +80 -0
  135. package/lib/test/helpers/hub-node-pair.js +227 -0
  136. package/lib/test/helpers/otlp-probe.js +107 -0
  137. package/lib/test/integration/androidDevices.spec.js +1 -1
  138. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  139. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  140. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  141. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  142. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  143. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  144. package/lib/test/unit/dashboard-events-device-logs.spec.js +74 -11
  145. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  146. package/lib/test/unit/database-check.spec.js +1 -0
  147. package/lib/test/unit/device-utils.spec.js +13 -4
  148. package/lib/test/unit/event-log-session-data.spec.js +11 -4
  149. package/lib/test/unit/failure-analysis.spec.js +22 -8
  150. package/lib/test/unit/failure-categories.spec.js +24 -22
  151. package/lib/test/unit/failure-categorization.spec.js +393 -0
  152. package/lib/test/unit/healing-tier-gate.spec.js +3 -2
  153. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +72 -1
  154. package/lib/test/unit/hub-node-iphone-readiness.spec.js +134 -0
  155. package/lib/test/unit/hub-node-session-device-logs.spec.js +194 -0
  156. package/lib/test/unit/hub-node-session-metrics.spec.js +24 -192
  157. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +8 -1
  158. package/lib/test/unit/interceptor/CertManager.spec.js +89 -0
  159. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  160. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  161. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  162. package/lib/test/unit/logcat-ws.spec.js +78 -37
  163. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  164. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  165. package/lib/test/unit/node-device-log-store.spec.js +102 -0
  166. package/lib/test/unit/node-device-logs-collector.spec.js +343 -0
  167. package/lib/test/unit/node-gateway-create-appium-server.spec.js +8 -1
  168. package/lib/test/unit/node-hub-session-owner.spec.js +8 -1
  169. package/lib/test/unit/node-session-device-logs-route.spec.js +103 -0
  170. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  171. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  172. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  173. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  174. package/lib/test/unit/proxy-option.spec.js +358 -0
  175. package/lib/test/unit/run-migrations-database.spec.js +440 -0
  176. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  177. package/lib/test/unit/run-migrations.test.js +264 -0
  178. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  179. package/lib/test/unit/selector-state-service.spec.js +72 -23
  180. package/lib/test/unit/selector-verification-clean-build.spec.js +1 -1
  181. package/lib/test/unit/selector-verification-job.spec.js +15 -6
  182. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  183. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  184. package/lib/test/unit/session-device-logs-hub-node.spec.js +340 -0
  185. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  186. package/lib/test/unit/session-device-logs-lifecycle.spec.js +137 -0
  187. package/lib/test/unit/session-device-logs.spec.js +43 -96
  188. package/lib/test/unit/session-end-failure-analysis.spec.js +5 -5
  189. package/lib/test/unit/session-log-indexes.spec.js +16 -1
  190. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  191. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  192. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  193. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  194. package/lib/test/unit/unexpected-shutdown-reason.spec.js +2 -2
  195. package/lib/tsconfig.tsbuildinfo +1 -1
  196. package/package.json +2 -1
  197. package/schema.json +11 -7
  198. package/scripts/dev/readme-screenshots.js +276 -23
  199. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  200. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  201. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  202. package/scripts/export-openapi.js +32 -0
  203. package/scripts/lib/openapi-for-site.js +79 -0
  204. package/lib/public/assets/index-BOxe43p1.js +0 -219
  205. package/lib/public/assets/session-detail-page-AZJE6XXE.js +0 -4
  206. package/lib/src/scripts/prepare-prisma.js +0 -89
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const chai_1 = require("chai");
13
+ const iosDriverLog_1 = require("../../src/services/logcat/iosDriverLog");
14
+ // The driver's own log class (test/helpers/driver-log.ts): an iPhone's and a
15
+ // simulator's `logs.syslog` extend it, so what Xenon reads is what the driver keeps.
16
+ const driver_log_1 = require("../helpers/driver-log");
17
+ const IPHONE_ERROR = 'Oct 5 20:31:40 iPhone SpringBoard(FrontBoard)[57] <Error>: Failed to launch com.example.shop';
18
+ const IPHONE_NOTICE = 'Oct 5 20:31:41 iPhone backboardd[61] <Notice>: touch began';
19
+ const SIM_FAULT = '2026-10-05 20:31:40.123 F Shop[4127:8812] (UIKitCore) [com.apple.UIKit:Crash] Terminating app';
20
+ const SIM_DEFAULT = '2026-10-05 20:31:40.200 Df SpringBoard[1234:5678] (FrontBoard) launched';
21
+ describe('iosDriverLog', () => {
22
+ describe('iosLineLevel', () => {
23
+ it("reads an iPhone's syslog level", () => {
24
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(IPHONE_ERROR)).to.equal('E');
25
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(IPHONE_NOTICE)).to.equal('I');
26
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(IPHONE_ERROR.replace('<Error>', '<Fault>'))).to.equal('F');
27
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(IPHONE_ERROR.replace('<Error>', '<Warning>'))).to.equal('W');
28
+ });
29
+ it("reads a simulator's compact type", () => {
30
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(SIM_FAULT)).to.equal('F');
31
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(SIM_DEFAULT)).to.equal('I');
32
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(SIM_DEFAULT.replace(' Df ', ' E '))).to.equal('E');
33
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)(SIM_DEFAULT.replace(' Df ', ' Db '))).to.equal('D');
34
+ });
35
+ it('takes a line with no level of its own as I', () => {
36
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineLevel)('\t0 Shop 0x0000000100abc123 main + 42')).to.equal('I');
37
+ });
38
+ });
39
+ describe('recordFromDriverLog', () => {
40
+ it('keeps the line as printed, at the time it reached the server', () => {
41
+ const rec = (0, iosDriverLog_1.recordFromDriverLog)({
42
+ timestamp: 1791000000123,
43
+ level: 'ALL',
44
+ message: IPHONE_ERROR,
45
+ });
46
+ (0, chai_1.expect)(rec).to.include({ ts: 1791000000123, level: 'E', message: IPHONE_ERROR });
47
+ (0, chai_1.expect)((0, iosDriverLog_1.iosLineText)(rec)).to.equal(IPHONE_ERROR);
48
+ });
49
+ it("drops what `log stream` prints before the simulator's first line", () => {
50
+ for (const banner of [
51
+ 'Filtering the log data using "subsystem != \\"com.apple.CoreTelephony\\""',
52
+ 'Timestamp Ty Process[PID:TID]',
53
+ ]) {
54
+ (0, chai_1.expect)((0, iosDriverLog_1.recordFromDriverLog)({ timestamp: 1, message: banner }), banner).to.equal(null);
55
+ }
56
+ });
57
+ it("keeps a crash report's continuation lines, and drops empty ones", () => {
58
+ (0, chai_1.expect)((0, iosDriverLog_1.recordFromDriverLog)({ timestamp: 1, message: '\tThread 0 Crashed:' })).to.not.equal(null);
59
+ (0, chai_1.expect)((0, iosDriverLog_1.recordFromDriverLog)({ timestamp: 1, message: ' ' })).to.equal(null);
60
+ (0, chai_1.expect)((0, iosDriverLog_1.recordFromDriverLog)(undefined)).to.equal(null);
61
+ });
62
+ });
63
+ describe("the driver's own log", () => {
64
+ it('is something Xenon can listen to', () => {
65
+ (0, chai_1.expect)((0, iosDriverLog_1.isDriverLog)(new driver_log_1.TestDriverLog())).to.equal(true);
66
+ (0, chai_1.expect)((0, iosDriverLog_1.isDriverLog)(undefined)).to.equal(false);
67
+ (0, chai_1.expect)((0, iosDriverLog_1.isDriverLog)({ on() { } })).to.equal(false);
68
+ });
69
+ it('gives the lines it holds, oldest first, without taking them from the test', () => __awaiter(void 0, void 0, void 0, function* () {
70
+ const driverLog = new driver_log_1.TestDriverLog();
71
+ driverLog.line('first');
72
+ driverLog.line('second');
73
+ driverLog.line('third');
74
+ (0, chai_1.expect)((0, iosDriverLog_1.bufferedDriverLog)(driverLog).map((e) => e.message)).to.deep.equal([
75
+ 'first',
76
+ 'second',
77
+ 'third',
78
+ ]);
79
+ // A test's own `getLog('syslog')` still gets every line.
80
+ const forTheTest = yield driverLog.getLogs();
81
+ (0, chai_1.expect)(forTheTest.map((e) => e.message)).to.deep.equal([
82
+ 'first',
83
+ 'second',
84
+ 'third',
85
+ ]);
86
+ }));
87
+ it('gives nothing for a log of another shape', () => {
88
+ (0, chai_1.expect)((0, iosDriverLog_1.bufferedDriverLog)({})).to.deep.equal([]);
89
+ (0, chai_1.expect)((0, iosDriverLog_1.bufferedDriverLog)({ logs: { rvalues: () => [1] } })).to.deep.equal([]);
90
+ (0, chai_1.expect)((0, iosDriverLog_1.bufferedDriverLog)({
91
+ logs: { rvalues: () => [1] },
92
+ _deserializeEntry: () => {
93
+ throw new Error('changed');
94
+ },
95
+ })).to.deep.equal([]);
96
+ });
97
+ });
98
+ // Lines as an iPhone 14 Plus (iOS 26.5) printed them during a session on
99
+ // Settings (com.apple.Preferences), pid 8429.
100
+ describe('IosAppLines', () => {
101
+ const at = (proc, text, level = 'Notice') => `Oct 5 23:48:33 Rabindras-iPhone ${proc} <${level}>: ${text}`;
102
+ const ANNOUNCE = at('runningboardd(RunningBoard)[34]', 'Acquiring assertion targeting [app<com.apple.Preferences(FEEDEEEE-DDDD)>:8429] from originator [osservice<com.apple.SpringBoard>:36947]');
103
+ // Written by the app's own code: no library, or the app's own binary.
104
+ const APP = at('Preferences[8429]', 'loading content for context');
105
+ const DAEMON = at('duetexpertd(DuetExpertCenter)[412]', 'Updating predictions');
106
+ const keeps = (f, m) => f.keeps(m, (0, iosDriverLog_1.iosLineLevel)(m));
107
+ it("keeps the app's own lines, the app manager's lines about it and faults, and leaves the rest", () => {
108
+ const f = new iosDriverLog_1.IosAppLines('com.apple.Preferences');
109
+ (0, chai_1.expect)(keeps(f, ANNOUNCE), 'the app manager names the app').to.equal(true);
110
+ (0, chai_1.expect)(keeps(f, APP), "the app's own code").to.equal(true);
111
+ (0, chai_1.expect)(keeps(f, at('Preferences(Preferences)[8429]', 'opened a pane')), 'its own binary').to.equal(true);
112
+ (0, chai_1.expect)(keeps(f, DAEMON), 'daemon chatter').to.equal(false);
113
+ (0, chai_1.expect)(keeps(f, at('wifid(WiFiPolicy)[61]', 'link quality dropped', 'Fault')), 'a fault').to.equal(true);
114
+ (0, chai_1.expect)(keeps(f, at('trustd(Security)[127]', 'SecKeyVerifySignature failed', 'Error'))).to.equal(false);
115
+ });
116
+ // Measured on Edge: 8,839 lines from its process, 5 of them its own code.
117
+ it("leaves out the frameworks' ordinary lines inside the app, and keeps their errors", () => {
118
+ const f = new iosDriverLog_1.IosAppLines('com.apple.Preferences');
119
+ f.learn(ANNOUNCE);
120
+ (0, chai_1.expect)(keeps(f, at('Preferences(UIKitCore)[8429]', 'Ending task with identifier 43'))).to.equal(false);
121
+ (0, chai_1.expect)(keeps(f, at('Preferences(CFNetwork)[8429]', 'Task finished', 'Error'))).to.equal(true);
122
+ (0, chai_1.expect)(keeps(f, at('Preferences(WebKit)[8429]', 'WebContent crashed', 'Fault'))).to.equal(true);
123
+ });
124
+ it('keeps a crash report or a memory kill that names the app by its executable', () => {
125
+ const f = new iosDriverLog_1.IosAppLines('com.apple.Preferences');
126
+ f.learn(ANNOUNCE);
127
+ f.learn(APP);
128
+ (0, chai_1.expect)(keeps(f, at('ReportCrash[9001]', 'Formulating fatal 309 report for corpse[8429] Preferences'))).to.equal(true);
129
+ (0, chai_1.expect)(keeps(f, at('ReportCrash[9001]', 'Saved crash report for Preferences[8429]'))).to.equal(true);
130
+ (0, chai_1.expect)(keeps(f, 'Oct 5 23:48:40 Rabindras-iPhone kernel[0] <Notice>: memorystatus: killing pid 8429 [Preferences]')).to.equal(true);
131
+ // A relaunch: a new pid, the same executable.
132
+ (0, chai_1.expect)(keeps(f, at('Preferences[8447]', 'scene connected'))).to.equal(true);
133
+ });
134
+ it("leaves out the daemons that only repeat the app's state", () => {
135
+ const f = new iosDriverLog_1.IosAppLines('com.apple.Preferences');
136
+ f.learn(ANNOUNCE);
137
+ for (const line of [
138
+ at('CommCenter(RunningBoardServices)[100]', 'Received state update for 8429 (app<com.apple.Preferences(FEEDEEEE)>, running-active-NotVisible'),
139
+ at('mobileassetd(MobileAssetDaemon)[120]', 'com.apple.Preferences issued query command'),
140
+ ]) {
141
+ (0, chai_1.expect)(keeps(f, line), line).to.equal(false);
142
+ }
143
+ (0, chai_1.expect)(keeps(f, at('SpringBoard(FrontBoard)[36947]', 'Application com.apple.Preferences exited'))).to.equal(true);
144
+ });
145
+ it('lets a line with no header follow the line before it', () => {
146
+ const f = new iosDriverLog_1.IosAppLines('com.apple.Preferences');
147
+ (0, chai_1.expect)(keeps(f, ANNOUNCE)).to.equal(true);
148
+ (0, chai_1.expect)(keeps(f, ' AssetLocale = "en_IN";')).to.equal(true);
149
+ (0, chai_1.expect)(keeps(f, DAEMON)).to.equal(false);
150
+ (0, chai_1.expect)(keeps(f, ' AssetLocale = "en_IN";')).to.equal(false);
151
+ });
152
+ it("reads a simulator's compact lines the same way", () => {
153
+ const f = new iosDriverLog_1.IosAppLines('com.example.shop');
154
+ const sim = (proc, text, type = 'Df') => `2026-10-05 20:31:40.123 ${type} ${proc} ${text}`;
155
+ (0, chai_1.expect)(keeps(f, sim('runningboardd[90:1200]', 'Acquiring assertion [app<com.example.shop(UUID)>:4127]'))).to.equal(true);
156
+ (0, chai_1.expect)(keeps(f, sim('Shop[4127:8812]', '(Shop) launched'))).to.equal(true);
157
+ (0, chai_1.expect)(keeps(f, sim('Shop[4127:8812]', '(UIKitCore) scene active'))).to.equal(false);
158
+ (0, chai_1.expect)(keeps(f, sim('Shop[4127:8812]', '(CFNetwork) task failed', 'E'))).to.equal(true);
159
+ (0, chai_1.expect)(keeps(f, sim('locationd[77:300]', 'region update'))).to.equal(false);
160
+ (0, chai_1.expect)(keeps(f, sim('Shop[4127:8812]', 'Terminating app', 'F'))).to.equal(true);
161
+ });
162
+ it("doesn't take a bundle id's dots as any character", () => {
163
+ const f = new iosDriverLog_1.IosAppLines('com.example.shop');
164
+ f.learn('Oct 5 23:48:33 iPhone runningboardd[34] <Notice>: [app<comXexampleXshop(UUID)>:5555]');
165
+ (0, chai_1.expect)(keeps(f, 'Oct 5 23:48:33 iPhone Other[5555] <Notice>: hello')).to.equal(false);
166
+ });
167
+ });
168
+ });
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = __importDefault(require("chai"));
50
+ const sinon_1 = __importDefault(require("sinon"));
51
+ const node_simctl_1 = require("node-simctl");
52
+ const DeviceUtils = __importStar(require("../../src/device-utils"));
53
+ const db_1 = require("../../src/data-service/db");
54
+ const device_service_1 = require("../../src/data-service/device-service");
55
+ const IOSDeviceManager_1 = __importDefault(require("../../src/device-managers/IOSDeviceManager"));
56
+ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
57
+ const WDAClient_1 = require("../../src/device-managers/ios/WDAClient");
58
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
59
+ const XenonCapabilityManager_1 = require("../../src/XenonCapabilityManager");
60
+ const test_container_1 = require("../helpers/test-container");
61
+ const loki_stores_1 = require("../helpers/loki-stores");
62
+ const scratch_database_1 = require("../helpers/scratch-database");
63
+ const expect = chai_1.default.expect;
64
+ /**
65
+ * A simulator's WebDriverAgent is the XCUITest driver's: the driver builds,
66
+ * installs and launches it on the session's `wdaLocalPort` once Appium
67
+ * creates the session, as plain Appium does. Xenon runs WDA only on an
68
+ * iPhone (go-ios `runwda`, for the live preview).
69
+ *
70
+ * Allocation used to require WDA to answer on the simulator's port before
71
+ * handing it over, and to "recover" it by rebooting the simulator, which
72
+ * starts no WDA. So every session on a simulator whose WDA wasn't already up
73
+ * was refused: "Device <udid> is unhealthy and could not be autonomously
74
+ * recovered."
75
+ */
76
+ describe('iOS simulator sessions: the driver starts WebDriverAgent', () => {
77
+ const scratch = (0, scratch_database_1.useScratchDatabase)();
78
+ (0, loki_stores_1.useLokiStores)();
79
+ const sandbox = sinon_1.default.createSandbox();
80
+ const HOST = 'http://127.0.0.1:4723';
81
+ const SIMULATOR = '35429994-B029-4AF4-9634-880B89DAC782';
82
+ const IPHONE = '00008150-000E78612168C01C';
83
+ const pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, {
84
+ platform: 'ios',
85
+ iosDeviceType: 'both',
86
+ });
87
+ const device = (udid, over = {}) => (Object.assign({ udid, name: 'iPhone 16 Pro', platform: 'ios', sdk: '18.4', host: HOST, busy: false, offline: false, userBlocked: false, state: 'Booted', deviceType: 'simulator', realDevice: false,
88
+ // Leased by discovery for a booted simulator; nothing listens on it
89
+ // until the driver launches WDA there.
90
+ wdaLocalPort: 8102, mjpegServerPort: 9102, totalUtilizationTimeMilliSec: 0, sessionStartTime: 0 }, over));
91
+ let wdaAsked;
92
+ let simctlCommands;
93
+ before(() => (0, test_container_1.resetTestContainer)());
94
+ after(() => (0, test_container_1.resetTestContainer)());
95
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
96
+ sandbox.stub(process, 'kill');
97
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
98
+ yield scratch.db.lease.deleteMany({});
99
+ sandbox.stub(DeviceUtils, 'setUtilizationTime').resolves();
100
+ sandbox.stub(IOSStreamService_1.default.prototype, 'getStreamStatus').returns(undefined);
101
+ // Nothing answers on the simulator's port, as on a freshly booted one.
102
+ wdaAsked = sandbox.stub(WDAClient_1.WDAClient.prototype, 'verifyWDAStatus').resolves(false);
103
+ // Every simctl call (shutdown, boot, list) goes through exec.
104
+ simctlCommands = [];
105
+ sandbox.stub(node_simctl_1.Simctl.prototype, 'exec').callsFake((subcommand) => __awaiter(void 0, void 0, void 0, function* () {
106
+ simctlCommands.push(subcommand);
107
+ return { stdout: '', stderr: '', code: 0 };
108
+ }));
109
+ (0, test_container_1.createTestXenonManager)(pluginArgs);
110
+ }));
111
+ afterEach(() => sandbox.restore());
112
+ const caps = (udid) => ({
113
+ alwaysMatch: {
114
+ platformName: 'iOS',
115
+ 'appium:automationName': 'XCUITest',
116
+ 'appium:udid': udid,
117
+ },
118
+ firstMatch: [{}],
119
+ });
120
+ it('allocates a booted simulator whose WebDriverAgent is not running yet', () => __awaiter(void 0, void 0, void 0, function* () {
121
+ yield (0, device_service_1.addNewDevice)([device(SIMULATOR)]);
122
+ const requested = caps(SIMULATOR);
123
+ const allocated = yield DeviceUtils.allocateDeviceForSession(requested, 2000, 100, pluginArgs);
124
+ expect(allocated.udid).to.equal(SIMULATOR);
125
+ expect(wdaAsked.called, 'asked WebDriverAgent').to.equal(false);
126
+ expect(simctlCommands, 'rebooted the simulator').to.not.include('shutdown');
127
+ expect(simctlCommands, 'rebooted the simulator').to.not.include('boot');
128
+ // The driver gets the port to launch its own WDA on, and no URL of one.
129
+ const fm = requested.firstMatch[0];
130
+ expect(fm['appium:wdaLocalPort']).to.equal(8102);
131
+ expect(fm).to.not.have.property('appium:webDriverAgentUrl');
132
+ }));
133
+ it('allocates a shut-down simulator, which the driver boots', () => __awaiter(void 0, void 0, void 0, function* () {
134
+ // Discovery leases no port to a shut-down simulator.
135
+ yield (0, device_service_1.addNewDevice)([
136
+ device(SIMULATOR, { state: 'Shutdown', wdaLocalPort: undefined, mjpegServerPort: 9103 }),
137
+ ]);
138
+ const requested = caps(SIMULATOR);
139
+ const allocated = yield DeviceUtils.allocateDeviceForSession(requested, 2000, 100, pluginArgs);
140
+ expect(allocated.udid).to.equal(SIMULATOR);
141
+ expect(wdaAsked.called).to.equal(false);
142
+ expect(simctlCommands).to.not.include('boot');
143
+ // It gets one now, for the driver's WDA.
144
+ expect(requested.firstMatch[0]['appium:wdaLocalPort']).to.be.a('number');
145
+ }));
146
+ describe('a live preview open on the phone', () => {
147
+ // The preview's stream entry for the phone: on an iPhone, the WDA Xenon
148
+ // runs; on a simulator, at most an attach to a driver's WDA, which goes
149
+ // with the session that started it.
150
+ beforeEach(() => {
151
+ IOSStreamService_1.default.prototype.getStreamStatus.returns({
152
+ status: 'running',
153
+ wdaPort: 8150,
154
+ });
155
+ });
156
+ it("never points a simulator session at it: the driver's own WDA, on the simulator's port", () => __awaiter(void 0, void 0, void 0, function* () {
157
+ const requested = caps(SIMULATOR);
158
+ yield (0, XenonCapabilityManager_1.iOSCapabilities)(requested, device(SIMULATOR));
159
+ const fm = requested.firstMatch[0];
160
+ expect(fm).to.not.have.property('appium:webDriverAgentUrl');
161
+ expect(fm['appium:wdaLocalPort']).to.equal(8102);
162
+ expect(fm['appium:mjpegServerPort']).to.equal(9102);
163
+ }));
164
+ it('still points an iPhone session at the WDA Xenon runs there', () => __awaiter(void 0, void 0, void 0, function* () {
165
+ const requested = caps(IPHONE);
166
+ yield (0, XenonCapabilityManager_1.iOSCapabilities)(requested, device(IPHONE, { realDevice: true, deviceType: 'real' }));
167
+ const fm = requested.firstMatch[0];
168
+ expect(fm['appium:webDriverAgentUrl']).to.equal('http://127.0.0.1:8150');
169
+ expect(fm).to.not.have.property('appium:wdaLocalPort');
170
+ }));
171
+ });
172
+ describe('an iPhone, whose WebDriverAgent Xenon runs', () => {
173
+ const iphone = () => device(IPHONE, { realDevice: true, deviceType: 'real' });
174
+ it('is still checked: ready when WebDriverAgent answers', () => __awaiter(void 0, void 0, void 0, function* () {
175
+ wdaAsked.resolves(true);
176
+ const manager = new IOSDeviceManager_1.default({ pluginArgs });
177
+ expect(yield manager.readyForSession(iphone())).to.equal(true);
178
+ expect(wdaAsked.calledOnceWith(IPHONE)).to.equal(true);
179
+ }));
180
+ it('is still refused when WebDriverAgent does not answer and cannot be started', () => __awaiter(void 0, void 0, void 0, function* () {
181
+ const manager = new IOSDeviceManager_1.default({ pluginArgs });
182
+ const recover = sandbox.stub(manager, 'recoverHealth').resolves(false);
183
+ expect(yield manager.readyForSession(iphone())).to.equal(false);
184
+ expect(recover.calledOnce).to.equal(true);
185
+ }));
186
+ });
187
+ });
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = require("chai");
50
+ const sinon_1 = __importDefault(require("sinon"));
51
+ const typedi_1 = require("typedi");
52
+ const DeviceService = __importStar(require("../../src/data-service/device-service"));
53
+ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
54
+ const event_manager_1 = require("../../src/dashboard/event-manager");
55
+ const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
56
+ const SessionMetricsService_1 = require("../../src/services/metrics/SessionMetricsService");
57
+ const PhoneNetworkRestore_1 = require("../../src/services/network/PhoneNetworkRestore");
58
+ const VideoPipelineService_1 = require("../../src/services/VideoPipelineService");
59
+ const LocalSession_1 = require("../../src/sessions/LocalSession");
60
+ const SessionManager_1 = require("../../src/sessions/SessionManager");
61
+ const container_registration_1 = require("../helpers/container-registration");
62
+ const scratch_database_1 = require("../helpers/scratch-database");
63
+ /**
64
+ * What Xenon does around a session on an iOS simulator, whose WebDriverAgent
65
+ * the XCUITest driver runs on this Mac.
66
+ *
67
+ * A session that passed every command ended failed: its end stopped a
68
+ * performance recording that Xenon starts only on an iPhone, the driver
69
+ * refuses that on a simulator without relaxed security, and the refused stop
70
+ * counted as a failed command of the session. Its video was never written:
71
+ * it was read from the iPhone stream's USB forward (iproxy), which on a
72
+ * simulator forwards nothing, while the simulator's own WebDriverAgent serves
73
+ * the picture on this Mac at the session's mjpegServerPort.
74
+ */
75
+ const SIMULATOR = '35429994-B029-4AF4-9634-880B89DAC782';
76
+ const IPHONE = '00008150-000E78612168C01C';
77
+ const device = (udid, realDevice) => ({
78
+ udid,
79
+ name: 'iPhone 16 Pro',
80
+ platform: 'ios',
81
+ sdk: '18.4',
82
+ host: 'http://127.0.0.1:4723',
83
+ realDevice,
84
+ deviceType: realDevice ? 'real' : 'simulator',
85
+ screenWidth: '402',
86
+ screenHeight: '874',
87
+ });
88
+ describe('iOS simulator sessions: around the session', function () {
89
+ this.timeout(30000);
90
+ afterEach(() => sinon_1.default.restore());
91
+ describe('the end of a session', () => {
92
+ (0, scratch_database_1.useScratchDatabase)();
93
+ let restoreRegs;
94
+ beforeEach(() => {
95
+ restoreRegs = (0, container_registration_1.saveRegistrations)(PhoneNetworkRestore_1.PhoneNetworkRestore, SessionMetricsService_1.SessionMetricsService);
96
+ typedi_1.Container.set(PhoneNetworkRestore_1.PhoneNetworkRestore, { restoreSession: sinon_1.default.stub().resolves() });
97
+ typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, { stop: sinon_1.default.stub().resolves() });
98
+ sinon_1.default.stub(DeviceService, 'releaseSessionDevices').resolves();
99
+ sinon_1.default.stub(event_manager_1.DASHBORD_EVENT_MANAGER, 'onSessionStopped').resolves();
100
+ sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'removeSession');
101
+ });
102
+ afterEach(() => restoreRegs());
103
+ /** Deletes a session on `phone`; resolves to whether its end stopped a performance recording. */
104
+ function endSessionOn(phone) {
105
+ return __awaiter(this, void 0, void 0, function* () {
106
+ const stopPerformanceRecording = sinon_1.default.stub().resolves(null);
107
+ const session = {
108
+ isStopping: false,
109
+ getId: () => 's-1',
110
+ getDevice: () => phone,
111
+ stopPerformanceRecording,
112
+ isVideoRecordingInProgress: () => false,
113
+ };
114
+ sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'getSession').returns(session);
115
+ yield typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService).deleteSession(sinon_1.default.stub().resolves(), 's-1');
116
+ return stopPerformanceRecording.called;
117
+ });
118
+ }
119
+ it('stops no performance recording on a simulator, where none was started', () => __awaiter(this, void 0, void 0, function* () {
120
+ (0, chai_1.expect)(yield endSessionOn(device(SIMULATOR, false))).to.equal(false);
121
+ }));
122
+ it("still stops an iPhone's, which Xenon started", () => __awaiter(this, void 0, void 0, function* () {
123
+ (0, chai_1.expect)(yield endSessionOn(device(IPHONE, true))).to.equal(true);
124
+ }));
125
+ });
126
+ describe('the session video', () => {
127
+ let startRecording;
128
+ let startStream;
129
+ beforeEach(() => {
130
+ startRecording = sinon_1.default.stub(VideoPipelineService_1.VideoPipelineService.prototype, 'startRecording').resolves();
131
+ startStream = sinon_1.default
132
+ .stub(IOSStreamService_1.default.prototype, 'startStream')
133
+ .resolves({ mjpegPort: 9150 });
134
+ });
135
+ const local = (phone) => new LocalSession_1.LocalSession({
136
+ sessionId: 's-video',
137
+ device: phone,
138
+ // The capabilities the driver answered the create with.
139
+ sessionResponse: { udid: phone.udid, wdaLocalPort: 8101, mjpegServerPort: 9101 },
140
+ xenonOption: {},
141
+ driver: { opts: { address: '127.0.0.1', port: 4723, basePath: '/wd/hub' }, sessions: {} },
142
+ });
143
+ it("is read from the simulator's own WebDriverAgent, at the session's MJPEG port", () => __awaiter(this, void 0, void 0, function* () {
144
+ yield local(device(SIMULATOR, false)).startVideoRecording();
145
+ (0, chai_1.expect)(startStream.called, 'started the iPhone stream').to.equal(false);
146
+ (0, chai_1.expect)(startRecording.calledOnce).to.equal(true);
147
+ (0, chai_1.expect)(startRecording.firstCall.args[0]).to.deep.include({
148
+ udid: SIMULATOR,
149
+ mjpegPort: 9101,
150
+ });
151
+ }));
152
+ it("is still read from an iPhone's stream", () => __awaiter(this, void 0, void 0, function* () {
153
+ yield local(device(IPHONE, true)).startVideoRecording();
154
+ (0, chai_1.expect)(startStream.calledOnceWith(IPHONE)).to.equal(true);
155
+ (0, chai_1.expect)(startRecording.firstCall.args[0]).to.deep.include({ udid: IPHONE, mjpegPort: 9150 });
156
+ }));
157
+ });
158
+ });